Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Razor Source Generator reference with one loaded from a VSIX #63912

Merged
merged 9 commits into from
Sep 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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.Threading;
using System.Threading.Tasks;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics
{
/// <summary>
/// Abstraction for testing purposes.
/// </summary>
internal interface IVisualStudioDiagnosticAnalyzerProviderFactory
tmat marked this conversation as resolved.
Show resolved Hide resolved
{
Task<VisualStudioDiagnosticAnalyzerProvider> GetOrCreateProviderAsync(CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// 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.Composition;
using System.Reflection;
using System.Threading.Tasks;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.VisualStudio.Shell;
using System.Threading;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics
{
internal partial class VisualStudioDiagnosticAnalyzerProvider
{
[Export(typeof(IVisualStudioDiagnosticAnalyzerProviderFactory)), Shared]
internal sealed class Factory : IVisualStudioDiagnosticAnalyzerProviderFactory
{
private readonly IThreadingContext _threadingContext;
private readonly IServiceProvider _serviceProvider;

private VisualStudioDiagnosticAnalyzerProvider? _lazyProvider;

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public Factory(IThreadingContext threadingContext, SVsServiceProvider serviceProvider)
{
_threadingContext = threadingContext;
_serviceProvider = serviceProvider;
}

public async Task<VisualStudioDiagnosticAnalyzerProvider> GetOrCreateProviderAsync(CancellationToken cancellationToken)
{
// the following code requires UI thread:
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

if (_lazyProvider != null)
{
return _lazyProvider;
}

var dte = (EnvDTE.DTE)_serviceProvider.GetService(typeof(EnvDTE.DTE));

// Microsoft.VisualStudio.ExtensionManager is non-versioned, so we need to dynamically load it, depending on the version of VS we are running on
// this will allow us to build once and deploy on different versions of VS SxS.
var vsDteVersion = Version.Parse(dte.Version.Split(' ')[0]); // DTE.Version is in the format of D[D[.D[D]]][ (?+)], so we need to split out the version part and check for uninitialized Major/Minor below

var assembly = Assembly.Load($"Microsoft.VisualStudio.ExtensionManager, Version={(vsDteVersion.Major == -1 ? 0 : vsDteVersion.Major)}.{(vsDteVersion.Minor == -1 ? 0 : vsDteVersion.Minor)}.0.0, PublicKeyToken=b03f5f7f11d50a3a");
var typeIExtensionContent = assembly.GetType("Microsoft.VisualStudio.ExtensionManager.IExtensionContent");
var type = assembly.GetType("Microsoft.VisualStudio.ExtensionManager.SVsExtensionManager");
var extensionManager = _serviceProvider.GetService(type);

return _lazyProvider = new VisualStudioDiagnosticAnalyzerProvider(extensionManager, typeIExtensionContent);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
using System;
using System.Composition;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
Expand All @@ -27,19 +29,16 @@ internal partial class VisualStudioDiagnosticAnalyzerProvider
internal sealed class WorkspaceEventListener : IEventListener<object>
{
private readonly IAsynchronousOperationListener _listener;
private readonly IThreadingContext _threadingContext;
private readonly IServiceProvider _serviceProvider;
private readonly IVisualStudioDiagnosticAnalyzerProviderFactory _providerFactory;

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public WorkspaceEventListener(
IThreadingContext threadingContext,
Shell.SVsServiceProvider serviceProvider,
IAsynchronousOperationListenerProvider listenerProvider)
IAsynchronousOperationListenerProvider listenerProvider,
IVisualStudioDiagnosticAnalyzerProviderFactory providerFactory)
{
_threadingContext = threadingContext;
_serviceProvider = serviceProvider;
_listener = listenerProvider.GetListener(nameof(Workspace));
_providerFactory = providerFactory;
}

public void StartListening(Workspace workspace, object serviceOpt)
Expand All @@ -57,37 +56,18 @@ private async Task InitializeWorkspaceAsync(ISolutionAnalyzerSetterWorkspaceServ
{
try
{
var provider = await CreateProviderAsync().ConfigureAwait(false);
var provider = await _providerFactory.GetOrCreateProviderAsync(CancellationToken.None).ConfigureAwait(false);

var references = provider.GetAnalyzerReferencesInExtensions();
LogWorkspaceAnalyzerCount(references.Length);
setter.SetAnalyzerReferences(references);
setter.SetAnalyzerReferences(references.SelectAsArray(referenceAndId => (AnalyzerReference)referenceAndId.reference));
}
catch (Exception e) when (FatalError.ReportAndPropagate(e, ErrorSeverity.Diagnostic))
{
throw ExceptionUtilities.Unreachable;
}
}

private async Task<VisualStudioDiagnosticAnalyzerProvider> CreateProviderAsync()
{
// the following code requires UI thread:
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync();

var dte = (EnvDTE.DTE)_serviceProvider.GetService(typeof(EnvDTE.DTE));

// Microsoft.VisualStudio.ExtensionManager is non-versioned, so we need to dynamically load it, depending on the version of VS we are running on
// this will allow us to build once and deploy on different versions of VS SxS.
var vsDteVersion = Version.Parse(dte.Version.Split(' ')[0]); // DTE.Version is in the format of D[D[.D[D]]][ (?+)], so we need to split out the version part and check for uninitialized Major/Minor below

var assembly = Assembly.Load($"Microsoft.VisualStudio.ExtensionManager, Version={(vsDteVersion.Major == -1 ? 0 : vsDteVersion.Major)}.{(vsDteVersion.Minor == -1 ? 0 : vsDteVersion.Minor)}.0.0, PublicKeyToken=b03f5f7f11d50a3a");
var typeIExtensionContent = assembly.GetType("Microsoft.VisualStudio.ExtensionManager.IExtensionContent");
var type = assembly.GetType("Microsoft.VisualStudio.ExtensionManager.SVsExtensionManager");
var extensionManager = _serviceProvider.GetService(type);

return new VisualStudioDiagnosticAnalyzerProvider(extensionManager, typeIExtensionContent);
}

private static void LogWorkspaceAnalyzerCount(int analyzerCount)
{
Logger.Log(FunctionId.DiagnosticAnalyzerService_Analyzers, KeyValueLogMessage.Create(m => m["AnalyzerCount"] = analyzerCount));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ internal partial class VisualStudioDiagnosticAnalyzerProvider
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)
{
Expand All @@ -37,16 +39,19 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty

_extensionManager = extensionManager;
_typeIExtensionContent = typeIExtensionContent;
_lazyAnalyzerReferences = new Lazy<ImmutableArray<(AnalyzerFileReference, string)>>(GetAnalyzerReferencesImpl);
}

// internal for testing
internal ImmutableArray<AnalyzerReference> GetAnalyzerReferencesInExtensions()
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 _ = PooledHashSet<string>.GetInstance(out var analyzePaths);
var _ = PooledDictionary<AnalyzerFileReference, string>.GetInstance(out var analyzePaths);

// var enabledExtensions = extensionManager.GetEnabledExtensions(AnalyzerContentTypeName);
var extensionManagerType = _extensionManager.GetType();
Expand All @@ -55,15 +60,13 @@ internal ImmutableArray<AnalyzerReference> GetAnalyzerReferencesInExtensions()

foreach (var extension in enabledExtensions)
{
// var name = extension.Header.LocalizedName;
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_LocalizedNameProperty = extension_HeaderType.GetRuntimeProperty("LocalizedName");
var name = extension_HeaderType_LocalizedNameProperty.GetValue(extension_Header) as string;
var extension_HeaderType_Identifier = extension_HeaderType.GetRuntimeProperty("Identifier");
var identifier = (string)extension_HeaderType_Identifier.GetValue(extension_Header);

// var extension_Content = extension.Content;
var extensionType_ContentProperty = extensionType.GetRuntimeProperty("Content");
var extension_Content = (IEnumerable<object>)extensionType_ContentProperty.GetValue(extension);

Expand All @@ -81,15 +84,16 @@ internal ImmutableArray<AnalyzerReference> GetAnalyzerReferencesInExtensions()
continue;
}

analyzePaths.Add(assemblyPath);
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);

return analyzePaths.SelectAsArray(path => (AnalyzerReference)new AnalyzerFileReference(path, AnalyzerAssemblyLoader));
// 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)
{
Expand All @@ -99,7 +103,7 @@ internal ImmutableArray<AnalyzerReference> GetAnalyzerReferencesInExtensions()
//
// 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<AnalyzerReference>.Empty;
return ImmutableArray<(AnalyzerFileReference, string)>.Empty;
}
}

Expand Down
Loading