From 74a446b7c182b2fbeacfcf85808ec0a524cda0b6 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 7 Aug 2025 16:35:17 +1000 Subject: [PATCH 1/7] Formalize misc files customizations, and add analyzer references support --- .../IMiscellaneousProjectInfoService.cs | 17 +++++++++ .../Workspace/MiscellaneousFileUtilities.cs | 23 +++++++---- .../Features/IRazorSourceGeneratorLocator.cs | 10 +++++ .../RazorMiscellaneousProjectInfoService.cs | 38 +++++++++++++++++++ 4 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 src/Features/Core/Portable/Workspace/IMiscellaneousProjectInfoService.cs create mode 100644 src/Tools/ExternalAccess/Razor/Features/IRazorSourceGeneratorLocator.cs create mode 100644 src/Tools/ExternalAccess/Razor/Features/RazorMiscellaneousProjectInfoService.cs diff --git a/src/Features/Core/Portable/Workspace/IMiscellaneousProjectInfoService.cs b/src/Features/Core/Portable/Workspace/IMiscellaneousProjectInfoService.cs new file mode 100644 index 0000000000000..7064cc397ed2f --- /dev/null +++ b/src/Features/Core/Portable/Workspace/IMiscellaneousProjectInfoService.cs @@ -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.Collections.Generic; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.Features.Workspaces; + +internal interface IMiscellaneousProjectInfoService : ILanguageService +{ + string ProjectLanguageOverride { get; } + bool AddAsAdditionalDocument { get; } + + IEnumerable? GetAnalyzerReferences(SolutionServices services); +} diff --git a/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs b/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs index d7784bcf900b7..8e051ced08388 100644 --- a/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs +++ b/src/Features/Core/Portable/Workspace/MiscellaneousFileUtilities.cs @@ -27,11 +27,18 @@ internal static ProjectInfo CreateMiscellaneousProjectInfoForDocument( var fileExtension = PathUtilities.GetExtension(filePath); var fileName = PathUtilities.GetFileName(filePath); - // For Razor files we need to override the language name to C# as thats what code is generated - var isRazor = languageInformation.LanguageName == "Razor"; - var languageName = isRazor ? LanguageNames.CSharp : languageInformation.LanguageName; - + var languageName = languageInformation.LanguageName; var languageServices = services.GetLanguageServices(languageName); + var miscellaneousProjectInfoService = languageServices.GetService(); + + if (miscellaneousProjectInfoService is not null) + { + // The MiscellaneousProjectInfoService can override the language name to use for the project, and therefore we have to re-get + // the right set of language services. + languageName = miscellaneousProjectInfoService.ProjectLanguageOverride; + languageServices = services.GetLanguageServices(languageName); + } + var compilationOptions = languageServices.GetService()?.GetDefaultCompilationOptions(); // 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( // a random GUID can be used. var assemblyName = Guid.NewGuid().ToString("N"); + var addAsAdditionalDocument = miscellaneousProjectInfoService?.AddAsAdditionalDocument ?? false; var projectInfo = ProjectInfo.Create( new ProjectInfo.ProjectAttributes( id: projectId, @@ -81,9 +89,10 @@ internal static ProjectInfo CreateMiscellaneousProjectInfoForDocument( hasAllInformation: sourceCodeKind == SourceCodeKind.Script), compilationOptions: compilationOptions, parseOptions: parseOptions, - documents: isRazor ? null : [documentInfo], - additionalDocuments: isRazor ? [documentInfo] : null, - metadataReferences: metadataReferences); + documents: addAsAdditionalDocument ? null : [documentInfo], + additionalDocuments: addAsAdditionalDocument ? [documentInfo] : null, + metadataReferences: metadataReferences, + analyzerReferences: miscellaneousProjectInfoService?.GetAnalyzerReferences(services)); return projectInfo; } diff --git a/src/Tools/ExternalAccess/Razor/Features/IRazorSourceGeneratorLocator.cs b/src/Tools/ExternalAccess/Razor/Features/IRazorSourceGeneratorLocator.cs new file mode 100644 index 0000000000000..985873ecf4f1a --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Features/IRazorSourceGeneratorLocator.cs @@ -0,0 +1,10 @@ +// 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. + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Features; + +internal interface IRazorSourceGeneratorLocator +{ + string GetGeneratorFilePath(); +} \ No newline at end of file diff --git a/src/Tools/ExternalAccess/Razor/Features/RazorMiscellaneousProjectInfoService.cs b/src/Tools/ExternalAccess/Razor/Features/RazorMiscellaneousProjectInfoService.cs new file mode 100644 index 0000000000000..835d02c2e3ae0 --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Features/RazorMiscellaneousProjectInfoService.cs @@ -0,0 +1,38 @@ +// 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.Collections.Generic; +using System.Composition; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Features.Workspaces; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Features; + +[Shared] +[ExportLanguageService(typeof(IMiscellaneousProjectInfoService), Constants.RazorLanguageName)] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class RazorMiscellaneousProjectInfoService([Import(AllowDefault = true)] Lazy? razorSourceGeneratorLocator) : IMiscellaneousProjectInfoService +{ + public string ProjectLanguageOverride => LanguageNames.CSharp; + + public bool AddAsAdditionalDocument => true; + + public IEnumerable? GetAnalyzerReferences(Host.SolutionServices services) + { + if (razorSourceGeneratorLocator is null) + { + return null; + } + + var filePath = razorSourceGeneratorLocator.Value.GetGeneratorFilePath(); + var loaderProvider = services.GetRequiredService(); + var reference = new AnalyzerFileReference(filePath, loaderProvider.SharedShadowCopyLoader); + + return [reference]; + } +} From 1dd730f5a47f5450848a79917aa7d1994180dc10 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 14 Aug 2025 13:41:28 +1000 Subject: [PATCH 2/7] Fix test --- ...ractLspMiscellaneousFilesWorkspaceTests.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/LanguageServer/Protocol.TestUtilities/AbstractLspMiscellaneousFilesWorkspaceTests.cs b/src/LanguageServer/Protocol.TestUtilities/AbstractLspMiscellaneousFilesWorkspaceTests.cs index ab29e37ae4f5d..b43d68ebd3fe9 100644 --- a/src/LanguageServer/Protocol.TestUtilities/AbstractLspMiscellaneousFilesWorkspaceTests.cs +++ b/src/LanguageServer/Protocol.TestUtilities/AbstractLspMiscellaneousFilesWorkspaceTests.cs @@ -2,14 +2,19 @@ // 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.Collections.Generic; +using System.Composition; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Features.Workspaces; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.Text; using Roslyn.LanguageServer.Protocol; using Roslyn.Test.Utilities; using Xunit; @@ -178,8 +183,10 @@ void M() [Theory, CombinatorialData] public async Task TestLooseFile_RazorFile(bool mutatingLspWorkspace) { + var composition = Composition.AddParts(typeof(TestRazorMiscellaneousProjectInfoService)); + // Create a server that supports LSP misc files and verify no misc files present. - await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); + await using var testLspServer = await CreateTestLspServerAsync(string.Empty, mutatingLspWorkspace, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }, composition); Assert.Null(await GetMiscellaneousDocumentAsync(testLspServer)); Assert.Null(await GetMiscellaneousAdditionalDocumentAsync(testLspServer)); @@ -397,4 +404,17 @@ private async Task AssertFileInMainWorkspaceAsync(TestLspServer testLspServer, D Contract.ThrowIfNull(result); return result; } + + // This is a test version of the real service which lives in the Razor EA, which is not referenced here + [PartNotDiscoverable] + [ExportLanguageService(typeof(IMiscellaneousProjectInfoService), "Razor"), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + private sealed class TestRazorMiscellaneousProjectInfoService() : IMiscellaneousProjectInfoService + { + public string ProjectLanguageOverride => LanguageNames.CSharp; + public bool AddAsAdditionalDocument => true; + + public IEnumerable? GetAnalyzerReferences(SolutionServices services) => null; + } } From 86c426ed6b91f1805daa79ee21fc9979538de4a5 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 15 Aug 2025 14:56:13 +1000 Subject: [PATCH 3/7] Export the analyzer loader for the Misc workspace so the Razor generator has a consistent ALC --- .../HostWorkspace/VSCodeAnalyzerLoaderProviderFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/VSCodeAnalyzerLoaderProviderFactory.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/VSCodeAnalyzerLoaderProviderFactory.cs index 623c912837f21..c630c544f5b6e 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/VSCodeAnalyzerLoaderProviderFactory.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/VSCodeAnalyzerLoaderProviderFactory.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; -[ExportWorkspaceService(typeof(IAnalyzerAssemblyLoaderProvider), [WorkspaceKind.Host]), Shared] +[ExportWorkspaceService(typeof(IAnalyzerAssemblyLoaderProvider), [WorkspaceKind.Host, WorkspaceKind.MiscellaneousFiles]), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class VSCodeAnalyzerLoaderProviderFactory( From 8f35c3fea61592801c5015b395febf7735f3edde Mon Sep 17 00:00:00 2001 From: David Wengier Date: Sat, 16 Aug 2025 17:02:00 +1000 Subject: [PATCH 4/7] Sneak in a sneaky test IVT --- .../Microsoft.CodeAnalysis.ExternalAccess.Razor.Features.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tools/ExternalAccess/Razor/Features/Microsoft.CodeAnalysis.ExternalAccess.Razor.Features.csproj b/src/Tools/ExternalAccess/Razor/Features/Microsoft.CodeAnalysis.ExternalAccess.Razor.Features.csproj index bc5712e05a6de..8d2e3ac5dbe0f 100644 --- a/src/Tools/ExternalAccess/Razor/Features/Microsoft.CodeAnalysis.ExternalAccess.Razor.Features.csproj +++ b/src/Tools/ExternalAccess/Razor/Features/Microsoft.CodeAnalysis.ExternalAccess.Razor.Features.csproj @@ -33,6 +33,7 @@ --> + From cc9177a963c2d8a0e89a66801f33fd81e29f2206 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Sat, 16 Aug 2025 17:08:18 +1000 Subject: [PATCH 5/7] Missed one --- .../Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj b/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj index ebb576317480d..0029e721fc979 100644 --- a/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj +++ b/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj @@ -50,6 +50,7 @@ + From 23d12a407ac7a1d199c4a44e0427e00a99120c21 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Sat, 16 Aug 2025 17:53:04 +1000 Subject: [PATCH 6/7] Revert "Missed one" This reverts commit cc9177a963c2d8a0e89a66801f33fd81e29f2206. --- .../Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj b/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj index 0029e721fc979..ebb576317480d 100644 --- a/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj +++ b/src/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj @@ -50,7 +50,6 @@ - From 9c7e33f152b7cf5b6a4fc50611a8eb314ceaaf8a Mon Sep 17 00:00:00 2001 From: David Wengier Date: Sat, 16 Aug 2025 17:53:18 +1000 Subject: [PATCH 7/7] Revert "Sneak in a sneaky test IVT" This reverts commit 8f35c3fea61592801c5015b395febf7735f3edde. --- .../Microsoft.CodeAnalysis.ExternalAccess.Razor.Features.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tools/ExternalAccess/Razor/Features/Microsoft.CodeAnalysis.ExternalAccess.Razor.Features.csproj b/src/Tools/ExternalAccess/Razor/Features/Microsoft.CodeAnalysis.ExternalAccess.Razor.Features.csproj index 8d2e3ac5dbe0f..bc5712e05a6de 100644 --- a/src/Tools/ExternalAccess/Razor/Features/Microsoft.CodeAnalysis.ExternalAccess.Razor.Features.csproj +++ b/src/Tools/ExternalAccess/Razor/Features/Microsoft.CodeAnalysis.ExternalAccess.Razor.Features.csproj @@ -33,7 +33,6 @@ --> -