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/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( 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; + } } 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]; + } +}