Skip to content
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
Expand Up @@ -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 [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ internal sealed partial class VisualStudioDiagnosticAnalyzerProvider : IHostDiag
{
private const string AnalyzerContentTypeName = "Microsoft.VisualStudio.Analyzer";

internal const string RazorContentTypeName = "Microsoft.VisualStudio.RazorAssembly";

/// <summary>
/// Loader for VSIX-based analyzers.
/// </summary>
Expand All @@ -31,6 +33,7 @@ internal sealed partial class VisualStudioDiagnosticAnalyzerProvider : IHostDiag
private readonly Type _typeIExtensionContent;

private readonly Lazy<ImmutableArray<(AnalyzerFileReference reference, string extensionId)>> _lazyAnalyzerReferences;
private readonly Lazy<ImmutableArray<(string path, string extensionId)>> _lazyRazorReferences;

// internal for testing
internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type typeIExtensionContent)
Expand All @@ -40,24 +43,28 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty

_extensionManager = extensionManager;
_typeIExtensionContent = typeIExtensionContent;
_lazyAnalyzerReferences = new Lazy<ImmutableArray<(AnalyzerFileReference, string)>>(GetAnalyzerReferencesImpl);
_lazyAnalyzerReferences = new Lazy<ImmutableArray<(AnalyzerFileReference, string)>>(() => GetExtensionContent(AnalyzerContentTypeName).SelectAsArray(c => (new AnalyzerFileReference(c.path, AnalyzerAssemblyLoader), c.extensionId)));
_lazyRazorReferences = new Lazy<ImmutableArray<(string, string)>>(() => 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<AnalyzerFileReference, string>.GetInstance(out var analyzePaths);
var _ = PooledDictionary<string, string>.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<object>)extensionManager_GetEnabledExtensionsMethod.Invoke(_extensionManager, [AnalyzerContentTypeName]);
var enabledExtensions = (IEnumerable<object>)extensionManager_GetEnabledExtensionsMethod.Invoke(_extensionManager, [contentTypeName]);

foreach (var extension in enabledExtensions)
{
Expand All @@ -73,7 +80,7 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty

foreach (var content in extension_Content)
{
if (!ShouldInclude(content))
if (!ShouldInclude(content, contentTypeName))
{
continue;
}
Expand All @@ -85,7 +92,7 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty
continue;
}

analyzePaths.Add(new AnalyzerFileReference(assemblyPath, AnalyzerAssemblyLoader), identifier);
analyzePaths.Add(assemblyPath, identifier);
}
}

Expand All @@ -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)
{
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,21 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics
Assert.Equal("TestAnalyzer", analyzers(0).ToString)
End Using
End Sub

<Fact>
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
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
{
({
Expand Down Expand Up @@ -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 =
{
({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"

<ImportingConstructor>
<Obsolete(MefConstruction.ImportingConstructorMessage, True)>
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ namespace Microsoft.CodeAnalysis.Workspaces.ProjectSystem;
internal interface IHostDiagnosticAnalyzerProvider
{
ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesInExtensions();

/// <summary>
/// Gets the path to any assemblies that represent the closure of razor compiler.
/// </summary>
ImmutableArray<(string path, string extensionId)> GetRazorAssembliesInExtensions();
}
Original file line number Diff line number Diff line change
Expand Up @@ -1190,9 +1190,9 @@ private OneOrMany<string> GetMappedAnalyzerPaths(string fullPath)

private OneOrMany<string> 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)
{
Expand Down
Loading