Skip to content

Commit afcf1aa

Browse files
committed
Separate out razor redirection:
- Razor is not going to be listed as an analyzer anymore - Add a version of the extension loading logic that loads the razor specified files in the razor VSIX - Add tests
1 parent 3e53da6 commit afcf1aa

File tree

5 files changed

+66
-88
lines changed

5 files changed

+66
-88
lines changed

src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/HostDiagnosticAnalyzerProvider.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ internal sealed class HostDiagnosticAnalyzerProvider(string? razorSourceGenerato
1313
{
1414
public ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesInExtensions() => [];
1515

16-
public ImmutableArray<string> GetContentInExtension(string extensionId, string contentTypeName)
16+
public ImmutableArray<(string path, string extensionId)> GetRazorReferencesInExtensions()
1717
{
18-
if (razorSourceGenerator is not null && extensionId == ProjectSystemProject.RazorVsixExtensionId && contentTypeName == ProjectSystemProject.RazorContentTypeName)
18+
if (razorSourceGenerator is not null)
1919
{
20-
return [razorSourceGenerator];
20+
return [(razorSourceGenerator, ProjectSystemProject.RazorVsixExtensionId)];
2121
}
2222
return [];
2323
}

src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs

Lines changed: 41 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Collections.Immutable;
8-
using System.Linq;
8+
using System.Configuration.Assemblies;
99
using System.Reflection;
10+
using ICSharpCode.Decompiler.CSharp.Syntax;
1011
using Microsoft.CodeAnalysis;
1112
using Microsoft.CodeAnalysis.Diagnostics;
1213
using Microsoft.CodeAnalysis.PooledObjects;
@@ -23,6 +24,8 @@ internal sealed partial class VisualStudioDiagnosticAnalyzerProvider : IHostDiag
2324
{
2425
private const string AnalyzerContentTypeName = "Microsoft.VisualStudio.Analyzer";
2526

27+
private const string RazorContentTypeName = "Microsoft.VisualStudio.RazorAssembly";
28+
2629
/// <summary>
2730
/// Loader for VSIX-based analyzers.
2831
/// </summary>
@@ -32,6 +35,7 @@ internal sealed partial class VisualStudioDiagnosticAnalyzerProvider : IHostDiag
3235
private readonly Type _typeIExtensionContent;
3336

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

3640
// internal for testing
3741
internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type typeIExtensionContent)
@@ -41,52 +45,56 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty
4145

4246
_extensionManager = extensionManager;
4347
_typeIExtensionContent = typeIExtensionContent;
44-
_lazyAnalyzerReferences = new Lazy<ImmutableArray<(AnalyzerFileReference, string)>>(GetAnalyzerReferencesImpl);
48+
_lazyAnalyzerReferences = new Lazy<ImmutableArray<(AnalyzerFileReference, string)>>(() => GetExtensionContent(AnalyzerContentTypeName).SelectAsArray(c => (new AnalyzerFileReference(c.path, AnalyzerAssemblyLoader), c.extensionId)));
49+
_lazyRazorReferences = new Lazy<ImmutableArray<(string, string)>>(() => GetExtensionContent(RazorContentTypeName));
4550
}
4651

4752
public ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesInExtensions()
4853
=> _lazyAnalyzerReferences.Value;
4954

50-
public ImmutableArray<string> GetContentInExtension(string extensionId, string contentTypeName)
51-
{
52-
try
53-
{
54-
var enabledExtensions = GetEnabledExtensions(contentTypeName);
55-
var extension = enabledExtensions.FirstOrDefault(e => string.Equals(GetExtensionIdentifier(e), extensionId, StringComparison.InvariantCultureIgnoreCase));
56-
if (extension is null)
57-
{
58-
return [];
59-
}
55+
public ImmutableArray<(string path, string extensionId)> GetRazorReferencesInExtensions()
56+
=> _lazyRazorReferences.Value;
6057

61-
return GetExtensionContent(extension, contentTypeName);
62-
}
63-
catch (TargetInvocationException ex) when (ex.InnerException is InvalidOperationException)
64-
{
65-
// this can be called from any thread, and extension manager could be disposed in the middle of us using it since
66-
// now all these are free-threaded and there is no central coordinator, or API or state is immutable that prevent states from
67-
// changing in the middle of others using it.
68-
//
69-
// fortunately, this only happens on disposing at shutdown, so we just catch the exception and silently swallow it.
70-
// we are about to shutdown anyway.
71-
return [];
72-
}
73-
}
74-
75-
private ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesImpl()
58+
private ImmutableArray<(string path, string extensionId)> GetExtensionContent(string contentTypeName)
7659
{
7760
try
7861
{
7962
// dynamic is weird. it can't see internal type with public interface even if callee is
8063
// implementation of the public interface in internal type. so we can't use dynamic here
81-
var _ = PooledDictionary<AnalyzerFileReference, string>.GetInstance(out var analyzePaths);
64+
var _ = PooledDictionary<string, string>.GetInstance(out var analyzePaths);
65+
66+
// var enabledExtensions = extensionManager.GetEnabledExtensions(contentTypeName);
67+
var extensionManagerType = _extensionManager.GetType();
68+
var extensionManager_GetEnabledExtensionsMethod = extensionManagerType.GetRuntimeMethod("GetEnabledExtensions", [typeof(string)]);
69+
var enabledExtensions = (IEnumerable<object>)extensionManager_GetEnabledExtensionsMethod.Invoke(_extensionManager, [contentTypeName]);
8270

83-
var enabledExtensions = GetEnabledExtensions(AnalyzerContentTypeName);
8471
foreach (var extension in enabledExtensions)
8572
{
86-
var identifier = GetExtensionIdentifier(extension);
87-
foreach (var assemblyPath in GetExtensionContent(extension, AnalyzerContentTypeName))
73+
var extensionType = extension.GetType();
74+
var extensionType_HeaderProperty = extensionType.GetRuntimeProperty("Header");
75+
var extension_Header = extensionType_HeaderProperty.GetValue(extension);
76+
var extension_HeaderType = extension_Header.GetType();
77+
var extension_HeaderType_Identifier = extension_HeaderType.GetRuntimeProperty("Identifier");
78+
var identifier = (string)extension_HeaderType_Identifier.GetValue(extension_Header);
79+
80+
var extensionType_ContentProperty = extensionType.GetRuntimeProperty("Content");
81+
var extension_Content = (IEnumerable<object>)extensionType_ContentProperty.GetValue(extension);
82+
83+
foreach (var content in extension_Content)
8884
{
89-
analyzePaths.Add(new AnalyzerFileReference(assemblyPath, AnalyzerAssemblyLoader), identifier);
85+
if (!ShouldInclude(content, contentTypeName))
86+
{
87+
continue;
88+
}
89+
90+
var extensionType_GetContentMethod = extensionType.GetRuntimeMethod("GetContentLocation", [_typeIExtensionContent]);
91+
if (extensionType_GetContentMethod?.Invoke(extension, [content]) is not string assemblyPath ||
92+
string.IsNullOrEmpty(assemblyPath))
93+
{
94+
continue;
95+
}
96+
97+
analyzePaths.Add(assemblyPath, identifier);
9098
}
9199
}
92100

@@ -95,7 +103,7 @@ public ImmutableArray<string> GetContentInExtension(string extensionId, string c
95103
GC.KeepAlive(enabledExtensions);
96104

97105
// Order for deterministic result.
98-
return analyzePaths.OrderBy((x, y) => string.CompareOrdinal(x.Key.FullPath, y.Key.FullPath)).SelectAsArray(entry => (entry.Key, entry.Value));
106+
return analyzePaths.OrderBy((x, y) => string.CompareOrdinal(x.Key, y.Key)).SelectAsArray(entry => (entry.Key, entry.Value));
99107
}
100108
catch (TargetInvocationException ex) when (ex.InnerException is InvalidOperationException)
101109
{
@@ -109,53 +117,6 @@ public ImmutableArray<string> GetContentInExtension(string extensionId, string c
109117
}
110118
}
111119

112-
private IEnumerable<object> GetEnabledExtensions(string contentTypeName)
113-
{
114-
// var enabledExtensions = extensionManager.GetEnabledExtensions(contentTypeName);
115-
var extensionManagerType = _extensionManager.GetType();
116-
var extensionManager_GetEnabledExtensionsMethod = extensionManagerType.GetRuntimeMethod("GetEnabledExtensions", [typeof(string)]);
117-
var enabledExtensions = (IEnumerable<object>)extensionManager_GetEnabledExtensionsMethod.Invoke(_extensionManager, [contentTypeName]);
118-
return enabledExtensions;
119-
}
120-
121-
private static string GetExtensionIdentifier(object extension)
122-
{
123-
var extensionType = extension.GetType();
124-
var extensionType_HeaderProperty = extensionType.GetRuntimeProperty("Header");
125-
var extension_Header = extensionType_HeaderProperty.GetValue(extension);
126-
var extension_HeaderType = extension_Header.GetType();
127-
var extension_HeaderType_Identifier = extension_HeaderType.GetRuntimeProperty("Identifier");
128-
var identifier = (string)extension_HeaderType_Identifier.GetValue(extension_Header);
129-
return identifier;
130-
}
131-
132-
private ImmutableArray<string> GetExtensionContent(object extension, string contentType)
133-
{
134-
var paths = ArrayBuilder<string>.GetInstance();
135-
var extensionType = extension.GetType();
136-
137-
var extensionType_ContentProperty = extensionType.GetRuntimeProperty("Content");
138-
var extension_Content = (IEnumerable<object>)extensionType_ContentProperty.GetValue(extension);
139-
140-
foreach (var content in extension_Content)
141-
{
142-
if (!ShouldInclude(content, contentType))
143-
{
144-
continue;
145-
}
146-
147-
var extensionType_GetContentMethod = extensionType.GetRuntimeMethod("GetContentLocation", [_typeIExtensionContent]);
148-
if (extensionType_GetContentMethod?.Invoke(extension, [content]) is not string assemblyPath ||
149-
string.IsNullOrEmpty(assemblyPath))
150-
{
151-
continue;
152-
}
153-
paths.Add(assemblyPath);
154-
}
155-
156-
return paths.ToImmutableAndFree();
157-
}
158-
159120
private static bool ShouldInclude(object content, string contentTypeName)
160121
{
161122
// var content_ContentTypeName = content.ContentTypeName;

src/VisualStudio/Core/Test/Diagnostics/VisualStudioDiagnosticAnalyzerProviderTests.vb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,21 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics
6060
Assert.Equal("TestAnalyzer", analyzers(0).ToString)
6161
End Using
6262
End Sub
63+
64+
<Fact>
65+
Public Sub GetRazorReferencesInExtensions()
66+
Dim extensionManager = New VisualStudioDiagnosticAnalyzerProvider(
67+
New MockExtensionManager({({"razorPath1", "razorPath2"}, "RazorVsix")}, contentType:="Microsoft.VisualStudio.RazorAssembly"),
68+
GetType(MockExtensionManager.MockContent))
69+
70+
Dim references = extensionManager.GetRazorReferencesInExtensions()
71+
72+
AssertEx.SetEqual(
73+
{
74+
Path.Combine(TempRoot.Root, "InstallPath\razorPath1"),
75+
Path.Combine(TempRoot.Root, "InstallPath\razorPath2")
76+
},
77+
references.Select(Function(pathAndId) pathAndId.path))
78+
End Sub
6379
End Class
6480
End Namespace

src/Workspaces/Core/Portable/Workspace/ProjectSystem/IHostDiagnosticAnalyzerProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ internal interface IHostDiagnosticAnalyzerProvider
1515
{
1616
ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesInExtensions();
1717

18-
ImmutableArray<string> GetContentInExtension(string extensionId, string contentTypeName);
18+
ImmutableArray<(string path, string extensionId)> GetRazorReferencesInExtensions();
1919
}

src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,7 +1176,6 @@ private OneOrMany<string> GetMappedAnalyzerPaths(string fullPath)
11761176
};
11771177

11781178
internal const string RazorVsixExtensionId = "Microsoft.VisualStudio.RazorExtension";
1179-
internal const string RazorContentTypeName = "Microsoft.VisualStudio.RazorCompilerAssembly";
11801179
private static readonly string s_razorSourceGeneratorSdkDirectory = CreateDirectoryPathFragment("Sdks", "Microsoft.NET.Sdk.Razor", "source-generators");
11811180
private static readonly ImmutableArray<string> s_razorSourceGeneratorAssemblyNames =
11821181
[
@@ -1191,14 +1190,16 @@ private OneOrMany<string> GetMappedAnalyzerPaths(string fullPath)
11911190

11921191
private OneOrMany<string> GetMappedRazorSourceGenerator(string fullPath)
11931192
{
1194-
var vsixRazorAssemblies = _hostInfo.HostDiagnosticAnalyzerProvider.GetContentInExtension(RazorVsixExtensionId, RazorContentTypeName);
1193+
var vsixRazorAnalyzers = _hostInfo.HostDiagnosticAnalyzerProvider.GetRazorReferencesInExtensions().SelectAsArray(
1194+
predicate: item => item.extensionId == RazorVsixExtensionId,
1195+
selector: item => item.path);
11951196

1196-
if (!vsixRazorAssemblies.IsEmpty)
1197+
if (!vsixRazorAnalyzers.IsEmpty)
11971198
{
11981199
if (s_razorSourceGeneratorAssemblyRootedFileNames.Any(
11991200
static (fileName, fullPath) => fullPath.EndsWith(fileName, StringComparison.OrdinalIgnoreCase), fullPath))
12001201
{
1201-
return OneOrMany.Create(vsixRazorAssemblies);
1202+
return OneOrMany.Create(vsixRazorAnalyzers);
12021203
}
12031204

12041205
return OneOrMany<string>.Empty;

0 commit comments

Comments
 (0)