Skip to content

Commit

Permalink
Replace Razor Source Generator reference with one loaded from a VSIX (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
tmat authored Sep 20, 2022
1 parent 5c96e99 commit c46ae61
Show file tree
Hide file tree
Showing 11 changed files with 333 additions and 104 deletions.
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
{
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

0 comments on commit c46ae61

Please sign in to comment.