diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/HostDiagnosticAnalyzerProvider.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/HostDiagnosticAnalyzerProvider.cs index 87a81f4de7dec..a7f193fe160d9 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/HostDiagnosticAnalyzerProvider.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/HostDiagnosticAnalyzerProvider.cs @@ -8,44 +8,17 @@ using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; -internal sealed class HostDiagnosticAnalyzerProvider : IHostDiagnosticAnalyzerProvider -{ - - private readonly ImmutableArray<(AnalyzerFileReference reference, string extensionId)> _analyzerReferences; - public HostDiagnosticAnalyzerProvider(string? razorSourceGenerator) - { - if (razorSourceGenerator == null || !File.Exists(razorSourceGenerator)) - { - _analyzerReferences = []; - } - else - { - _analyzerReferences = [( - new AnalyzerFileReference(razorSourceGenerator, new SimpleAnalyzerAssemblyLoader()), - ProjectSystemProject.RazorVsixExtensionId - )]; - } - } - - public ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesInExtensions() - { - return _analyzerReferences; - } +internal sealed class HostDiagnosticAnalyzerProvider(string? razorSourceGenerator) : IHostDiagnosticAnalyzerProvider +{ + public ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesInExtensions() => []; - private sealed class SimpleAnalyzerAssemblyLoader : IAnalyzerAssemblyLoader + public ImmutableArray<(string path, string extensionId)> GetRazorAssembliesInExtensions() { - public void AddDependencyLocation(string fullPath) - { - // This method is used to add a path that should be probed for analyzer dependencies. - // In this simple implementation, we do nothing. - } - - public Assembly LoadFromPath(string fullPath) + if (File.Exists(razorSourceGenerator)) { - // This method is used to load an analyzer assembly from the specified path. - // In this simple implementation, we use Assembly.LoadFrom to load the assembly. - return Assembly.LoadFrom(fullPath); + return [(razorSourceGenerator, ProjectSystemProject.RazorVsixExtensionId)]; } + return []; } } diff --git a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs index 95a248768a7b3..dd4f125902eb2 100644 --- a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs +++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs @@ -22,6 +22,8 @@ internal sealed partial class VisualStudioDiagnosticAnalyzerProvider : IHostDiag { private const string AnalyzerContentTypeName = "Microsoft.VisualStudio.Analyzer"; + internal const string RazorContentTypeName = "Microsoft.VisualStudio.RazorAssembly"; + /// /// Loader for VSIX-based analyzers. /// @@ -31,6 +33,7 @@ internal sealed partial class VisualStudioDiagnosticAnalyzerProvider : IHostDiag private readonly Type _typeIExtensionContent; private readonly Lazy> _lazyAnalyzerReferences; + private readonly Lazy> _lazyRazorReferences; // internal for testing internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type typeIExtensionContent) @@ -40,24 +43,28 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty _extensionManager = extensionManager; _typeIExtensionContent = typeIExtensionContent; - _lazyAnalyzerReferences = new Lazy>(GetAnalyzerReferencesImpl); + _lazyAnalyzerReferences = new Lazy>(() => GetExtensionContent(AnalyzerContentTypeName).SelectAsArray(c => (new AnalyzerFileReference(c.path, AnalyzerAssemblyLoader), c.extensionId))); + _lazyRazorReferences = new Lazy>(() => GetExtensionContent(RazorContentTypeName)); } public ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesInExtensions() => _lazyAnalyzerReferences.Value; - private ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesImpl() + public ImmutableArray<(string path, string extensionId)> GetRazorAssembliesInExtensions() + => _lazyRazorReferences.Value; + + private ImmutableArray<(string path, string extensionId)> GetExtensionContent(string contentTypeName) { 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.GetInstance(out var analyzePaths); + var _ = PooledDictionary.GetInstance(out var analyzePaths); - // var enabledExtensions = extensionManager.GetEnabledExtensions(AnalyzerContentTypeName); + // var enabledExtensions = extensionManager.GetEnabledExtensions(contentTypeName); var extensionManagerType = _extensionManager.GetType(); var extensionManager_GetEnabledExtensionsMethod = extensionManagerType.GetRuntimeMethod("GetEnabledExtensions", [typeof(string)]); - var enabledExtensions = (IEnumerable)extensionManager_GetEnabledExtensionsMethod.Invoke(_extensionManager, [AnalyzerContentTypeName]); + var enabledExtensions = (IEnumerable)extensionManager_GetEnabledExtensionsMethod.Invoke(_extensionManager, [contentTypeName]); foreach (var extension in enabledExtensions) { @@ -73,7 +80,7 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty foreach (var content in extension_Content) { - if (!ShouldInclude(content)) + if (!ShouldInclude(content, contentTypeName)) { continue; } @@ -85,7 +92,7 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty continue; } - analyzePaths.Add(new AnalyzerFileReference(assemblyPath, AnalyzerAssemblyLoader), identifier); + analyzePaths.Add(assemblyPath, identifier); } } @@ -94,7 +101,7 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty 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)); + return analyzePaths.OrderBy((x, y) => string.CompareOrdinal(x.Key, y.Key)).SelectAsArray(entry => (entry.Key, entry.Value)); } catch (TargetInvocationException ex) when (ex.InnerException is InvalidOperationException) { @@ -108,13 +115,13 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty } } - private static bool ShouldInclude(object content) + private static bool ShouldInclude(object content, string contentTypeName) { // 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); + return string.Equals(content_ContentTypeName, contentTypeName, StringComparison.InvariantCultureIgnoreCase); } } diff --git a/src/VisualStudio/Core/Test/Diagnostics/VisualStudioDiagnosticAnalyzerProviderTests.vb b/src/VisualStudio/Core/Test/Diagnostics/VisualStudioDiagnosticAnalyzerProviderTests.vb index bdd55a2ac41c4..8dc1930f29df0 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/VisualStudioDiagnosticAnalyzerProviderTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/VisualStudioDiagnosticAnalyzerProviderTests.vb @@ -60,5 +60,21 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Assert.Equal("TestAnalyzer", analyzers(0).ToString) End Using End Sub + + + Public Sub GetRazorReferencesInExtensions() + Dim extensionManager = New VisualStudioDiagnosticAnalyzerProvider( + New MockExtensionManager({({"razorPath1", "razorPath2"}, "RazorVsix")}, contentType:="Microsoft.VisualStudio.RazorAssembly"), + GetType(MockExtensionManager.MockContent)) + + Dim references = extensionManager.GetRazorAssembliesInExtensions() + + AssertEx.SetEqual( + { + Path.Combine(TempRoot.Root, "InstallPath\razorPath1"), + Path.Combine(TempRoot.Root, "InstallPath\razorPath2") + }, + references.Select(Function(pathAndId) pathAndId.path)) + End Sub End Class End Namespace diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb index c0d0b85ee28c5..0c6b438aebe8d 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb @@ -102,6 +102,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Public Async Function RazorSourceGenerator_FromVsix() As Task Using environment = New TestEnvironment() Dim providerFactory = DirectCast(environment.ExportProvider.GetExportedValue(Of IVisualStudioDiagnosticAnalyzerProviderFactory), MockVisualStudioDiagnosticAnalyzerProviderFactory) + providerFactory.ContentTypeName = VisualStudioDiagnosticAnalyzerProvider.RazorContentTypeName providerFactory.Extensions = { ({ @@ -184,6 +185,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Public Async Function RazorSourceGenerator_FromSdk() As Task Using environment = New TestEnvironment() Dim providerFactory = DirectCast(environment.ExportProvider.GetExportedValue(Of IVisualStudioDiagnosticAnalyzerProviderFactory), MockVisualStudioDiagnosticAnalyzerProviderFactory) + providerFactory.ContentTypeName = VisualStudioDiagnosticAnalyzerProvider.RazorContentTypeName providerFactory.Extensions = { ({ diff --git a/src/VisualStudio/TestUtilities2/MockVisualStudioDiagnosticAnalyzerProviderFactory.vb b/src/VisualStudio/TestUtilities2/MockVisualStudioDiagnosticAnalyzerProviderFactory.vb index 2bca62d09f7d7..821233af421fa 100644 --- a/src/VisualStudio/TestUtilities2/MockVisualStudioDiagnosticAnalyzerProviderFactory.vb +++ b/src/VisualStudio/TestUtilities2/MockVisualStudioDiagnosticAnalyzerProviderFactory.vb @@ -15,13 +15,15 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Public Property Extensions As (Paths As String(), Id As String)() = Array.Empty(Of (Paths As String(), Id As String))() + Public Property ContentTypeName As String = "Microsoft.VisualStudio.Analyzer" + Public Sub New() End Sub Public Function GetOrCreateProviderAsync(cancellationToken As CancellationToken) As Task(Of VisualStudioDiagnosticAnalyzerProvider) Implements IVisualStudioDiagnosticAnalyzerProviderFactory.GetOrCreateProviderAsync - Return Task.FromResult(New VisualStudioDiagnosticAnalyzerProvider(New MockExtensionManager(Extensions), GetType(MockExtensionManager.MockContent))) + Return Task.FromResult(New VisualStudioDiagnosticAnalyzerProvider(New MockExtensionManager(Extensions, ContentTypeName), GetType(MockExtensionManager.MockContent))) End Function End Class End Namespace diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/IHostDiagnosticAnalyzerProvider.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/IHostDiagnosticAnalyzerProvider.cs index 26854deac43a7..709388e199aed 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/IHostDiagnosticAnalyzerProvider.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/IHostDiagnosticAnalyzerProvider.cs @@ -14,4 +14,9 @@ namespace Microsoft.CodeAnalysis.Workspaces.ProjectSystem; internal interface IHostDiagnosticAnalyzerProvider { ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesInExtensions(); + + /// + /// Gets the path to any assemblies that represent the closure of razor compiler. + /// + ImmutableArray<(string path, string extensionId)> GetRazorAssembliesInExtensions(); } diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index fdf1663a2a0b5..9e8b4a378196d 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -1190,9 +1190,9 @@ private OneOrMany GetMappedAnalyzerPaths(string fullPath) private OneOrMany GetMappedRazorSourceGenerator(string fullPath) { - var vsixRazorAnalyzers = _hostInfo.HostDiagnosticAnalyzerProvider.GetAnalyzerReferencesInExtensions().SelectAsArray( + var vsixRazorAnalyzers = _hostInfo.HostDiagnosticAnalyzerProvider.GetRazorAssembliesInExtensions().SelectAsArray( predicate: item => item.extensionId == RazorVsixExtensionId, - selector: item => item.reference.FullPath); + selector: item => item.path); if (!vsixRazorAnalyzers.IsEmpty) {