Skip to content

Commit b7353b8

Browse files
authored
Allow Razor to hook up the source generator in misc files (#79891)
Part of dotnet/vscode-csharp#8512 Tracked by dotnet/razor#9519 and dotnet/razor#11833 This removes the `isRazor` hackiness in misc files, and allows Razor to provide a source generator reference to the project. Needs work on Razor not just to implement the service, which is trivial, but because the source generator doesn't actually support misc files at the moment :)
2 parents 4782ad2 + 9c7e33f commit b7353b8

File tree

6 files changed

+104
-10
lines changed

6 files changed

+104
-10
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Generic;
6+
using Microsoft.CodeAnalysis.Diagnostics;
7+
using Microsoft.CodeAnalysis.Host;
8+
9+
namespace Microsoft.CodeAnalysis.Features.Workspaces;
10+
11+
internal interface IMiscellaneousProjectInfoService : ILanguageService
12+
{
13+
string ProjectLanguageOverride { get; }
14+
bool AddAsAdditionalDocument { get; }
15+
16+
IEnumerable<AnalyzerReference>? GetAnalyzerReferences(SolutionServices services);
17+
}

src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,18 @@ internal static ProjectInfo CreateMiscellaneousProjectInfoForDocument(
2727
var fileExtension = PathUtilities.GetExtension(filePath);
2828
var fileName = PathUtilities.GetFileName(filePath);
2929

30-
// For Razor files we need to override the language name to C# as thats what code is generated
31-
var isRazor = languageInformation.LanguageName == "Razor";
32-
var languageName = isRazor ? LanguageNames.CSharp : languageInformation.LanguageName;
33-
30+
var languageName = languageInformation.LanguageName;
3431
var languageServices = services.GetLanguageServices(languageName);
32+
var miscellaneousProjectInfoService = languageServices.GetService<IMiscellaneousProjectInfoService>();
33+
34+
if (miscellaneousProjectInfoService is not null)
35+
{
36+
// The MiscellaneousProjectInfoService can override the language name to use for the project, and therefore we have to re-get
37+
// the right set of language services.
38+
languageName = miscellaneousProjectInfoService.ProjectLanguageOverride;
39+
languageServices = services.GetLanguageServices(languageName);
40+
}
41+
3542
var compilationOptions = languageServices.GetService<ICompilationFactoryService>()?.GetDefaultCompilationOptions();
3643

3744
// Use latest language version which is more permissive, as we cannot find out language version of the project which the file belongs to
@@ -67,6 +74,7 @@ internal static ProjectInfo CreateMiscellaneousProjectInfoForDocument(
6774
// a random GUID can be used.
6875
var assemblyName = Guid.NewGuid().ToString("N");
6976

77+
var addAsAdditionalDocument = miscellaneousProjectInfoService?.AddAsAdditionalDocument ?? false;
7078
var projectInfo = ProjectInfo.Create(
7179
new ProjectInfo.ProjectAttributes(
7280
id: projectId,
@@ -81,9 +89,10 @@ internal static ProjectInfo CreateMiscellaneousProjectInfoForDocument(
8189
hasAllInformation: sourceCodeKind == SourceCodeKind.Script),
8290
compilationOptions: compilationOptions,
8391
parseOptions: parseOptions,
84-
documents: isRazor ? null : [documentInfo],
85-
additionalDocuments: isRazor ? [documentInfo] : null,
86-
metadataReferences: metadataReferences);
92+
documents: addAsAdditionalDocument ? null : [documentInfo],
93+
additionalDocuments: addAsAdditionalDocument ? [documentInfo] : null,
94+
metadataReferences: metadataReferences,
95+
analyzerReferences: miscellaneousProjectInfoService?.GetAnalyzerReferences(services));
8796

8897
return projectInfo;
8998
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace;
1313

14-
[ExportWorkspaceService(typeof(IAnalyzerAssemblyLoaderProvider), [WorkspaceKind.Host]), Shared]
14+
[ExportWorkspaceService(typeof(IAnalyzerAssemblyLoaderProvider), [WorkspaceKind.Host, WorkspaceKind.MiscellaneousFiles]), Shared]
1515
[method: ImportingConstructor]
1616
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
1717
internal sealed class VSCodeAnalyzerLoaderProviderFactory(

src/LanguageServer/Protocol.TestUtilities/AbstractLspMiscellaneousFilesWorkspaceTests.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
6+
using System.Collections.Generic;
7+
using System.Composition;
58
using System.IO;
69
using System.Linq;
710
using System.Threading;
811
using System.Threading.Tasks;
12+
using Microsoft.CodeAnalysis.Diagnostics;
13+
using Microsoft.CodeAnalysis.Features.Workspaces;
914
using Microsoft.CodeAnalysis.Host;
15+
using Microsoft.CodeAnalysis.Host.Mef;
1016
using Microsoft.CodeAnalysis.Shared.TestHooks;
1117
using Microsoft.CodeAnalysis.Test.Utilities;
12-
using Microsoft.CodeAnalysis.Text;
1318
using Roslyn.LanguageServer.Protocol;
1419
using Roslyn.Test.Utilities;
1520
using Xunit;
@@ -178,8 +183,10 @@ void M()
178183
[Theory, CombinatorialData]
179184
public async Task TestLooseFile_RazorFile(bool mutatingLspWorkspace)
180185
{
186+
var composition = Composition.AddParts(typeof(TestRazorMiscellaneousProjectInfoService));
187+
181188
// Create a server that supports LSP misc files and verify no misc files present.
182-
await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer });
189+
await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }, composition);
183190
Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer));
184191
Assert.Null(await GetMiscellaneousAdditionalDocumentAsync(testLspServer));
185192

@@ -397,4 +404,17 @@ private async Task AssertFileInMainWorkspaceAsync(TestLspServer testLspServer, D
397404
Contract.ThrowIfNull(result);
398405
return result;
399406
}
407+
408+
// This is a test version of the real service which lives in the Razor EA, which is not referenced here
409+
[PartNotDiscoverable]
410+
[ExportLanguageService(typeof(IMiscellaneousProjectInfoService), "Razor"), Shared]
411+
[method: ImportingConstructor]
412+
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
413+
private sealed class TestRazorMiscellaneousProjectInfoService() : IMiscellaneousProjectInfoService
414+
{
415+
public string ProjectLanguageOverride => LanguageNames.CSharp;
416+
public bool AddAsAdditionalDocument => true;
417+
418+
public IEnumerable<AnalyzerReference>? GetAnalyzerReferences(SolutionServices services) => null;
419+
}
400420
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Features;
6+
7+
internal interface IRazorSourceGeneratorLocator
8+
{
9+
string GetGeneratorFilePath();
10+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Composition;
8+
using Microsoft.CodeAnalysis.Diagnostics;
9+
using Microsoft.CodeAnalysis.Features.Workspaces;
10+
using Microsoft.CodeAnalysis.Host;
11+
using Microsoft.CodeAnalysis.Host.Mef;
12+
13+
namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Features;
14+
15+
[Shared]
16+
[ExportLanguageService(typeof(IMiscellaneousProjectInfoService), Constants.RazorLanguageName)]
17+
[method: ImportingConstructor]
18+
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
19+
internal sealed class RazorMiscellaneousProjectInfoService([Import(AllowDefault = true)] Lazy<IRazorSourceGeneratorLocator>? razorSourceGeneratorLocator) : IMiscellaneousProjectInfoService
20+
{
21+
public string ProjectLanguageOverride => LanguageNames.CSharp;
22+
23+
public bool AddAsAdditionalDocument => true;
24+
25+
public IEnumerable<AnalyzerReference>? GetAnalyzerReferences(Host.SolutionServices services)
26+
{
27+
if (razorSourceGeneratorLocator is null)
28+
{
29+
return null;
30+
}
31+
32+
var filePath = razorSourceGeneratorLocator.Value.GetGeneratorFilePath();
33+
var loaderProvider = services.GetRequiredService<IAnalyzerAssemblyLoaderProvider>();
34+
var reference = new AnalyzerFileReference(filePath, loaderProvider.SharedShadowCopyLoader);
35+
36+
return [reference];
37+
}
38+
}

0 commit comments

Comments
 (0)