From 5cef8f96e1d169290397810e16884eb007420749 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Tue, 14 Jun 2022 19:48:58 -0700 Subject: [PATCH 1/3] Add support for additional file diagnostics in workspace pull --- .../Handlers/CodeActions/CodeActionHelpers.cs | 2 +- .../TestUtilities/IsExternalInit.cs | 11 ++ ...rverProtocolTests.InitializationOptions.cs | 22 ++++ .../AbstractLanguageServerProtocolTests.cs | 94 +++++++-------- .../Protocol/Extensions/Extensions.cs | 13 +- .../AbstractPullDiagnosticHandler.cs | 25 +++- .../DocumentPullDiagnosticHandler.cs | 6 + ...erimentalDocumentPullDiagnosticsHandler.cs | 16 +-- ...rimentalWorkspacePullDiagnosticsHandler.cs | 24 ++-- .../WorkspacePullDiagnosticHandler.cs | 26 ++-- .../CodeActions/CodeActionsTests.cs | 17 ++- .../Definitions/GoToDefinitionTests.cs | 10 +- .../AbstractPullDiagnosticTestsBase.cs | 54 ++++----- .../AdditionalFileDiagnosticsTests.cs | 113 ++++++++++++++++++ .../Diagnostics/PullDiagnosticTests.cs | 22 ++-- .../WorkspaceProjectDiagnosticsTests.cs | 11 +- .../ProtocolUnitTests/Hover/HoverTests.cs | 2 +- .../LspMiscellaneousFilesWorkspaceTests.cs | 12 +- .../OnAutoInsert/OnAutoInsertTests.cs | 4 +- .../FindAllReferencesHandlerTests.cs | 2 +- .../Workspaces/LspWorkspaceManagerTests.cs | 6 +- 21 files changed, 336 insertions(+), 156 deletions(-) create mode 100644 src/EditorFeatures/TestUtilities/IsExternalInit.cs create mode 100644 src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs create mode 100644 src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs diff --git a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionHelpers.cs b/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionHelpers.cs index 571cdda77f6b0..3a957e6848bea 100644 --- a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionHelpers.cs +++ b/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionHelpers.cs @@ -154,7 +154,7 @@ static VSInternalCodeAction[] GenerateNestedVSCodeActions( static LSP.Diagnostic[]? GetApplicableDiagnostics(LSP.CodeActionContext context, IUnifiedSuggestedAction action) { - if (action is UnifiedCodeFixSuggestedAction codeFixAction) + if (action is UnifiedCodeFixSuggestedAction codeFixAction && context.Diagnostics != null) { // Associate the diagnostics from the request that match the diagnostic fixed by the code action by ID. // The request diagnostics are already restricted to the code fix location by the request. diff --git a/src/EditorFeatures/TestUtilities/IsExternalInit.cs b/src/EditorFeatures/TestUtilities/IsExternalInit.cs new file mode 100644 index 0000000000000..a45663ee0a382 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/IsExternalInit.cs @@ -0,0 +1,11 @@ +// 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 System.Runtime.CompilerServices +{ + // Used to compile against C# 9 in a netstandard2.0 + internal class IsExternalInit + { + } +} diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs new file mode 100644 index 0000000000000..7011e81bbcfc3 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs @@ -0,0 +1,22 @@ +// 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 Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.Options; +using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Roslyn.Test.Utilities +{ + public abstract partial class AbstractLanguageServerProtocolTests + { + internal record struct InitializationOptions() + { + internal string[] SourceGeneratedMarkups { get; init; } = Array.Empty(); + internal LSP.ClientCapabilities ClientCapabilities { get; init; } = new LSP.ClientCapabilities(); + internal WellKnownLspServerKinds ServerKind { get; init; } = WellKnownLspServerKinds.AlwaysActiveVSLspServer; + internal Action? OptionUpdater { get; init; } = null; + } + } +} diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs index 69035b80e0f89..4ae7cd6b4b0fb 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs +++ b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs @@ -99,6 +99,9 @@ protected class OrderLocations : Comparer protected virtual TestComposition Composition => s_composition; + private protected virtual TestAnalyzerReferenceByLanguage TestAnalyzerReferences + => new(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()); + protected static LSP.ClientCapabilities CapabilitiesWithVSExtensions => new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }; /// @@ -282,50 +285,42 @@ protected static LSP.TextEdit GenerateTextEdit(string newText, int startLine, in private protected static CodeActionResolveData CreateCodeActionResolveData(string uniqueIdentifier, LSP.Location location, IEnumerable? customTags = null) => new CodeActionResolveData(uniqueIdentifier, customTags.ToImmutableArrayOrEmpty(), location.Range, CreateTextDocumentIdentifier(location.Uri)); - /// - /// Creates an LSP server backed by a workspace instance with a solution containing the markup. - /// - protected Task CreateTestLspServerAsync(string markup, LSP.ClientCapabilities? clientCapabilities = null) - => CreateTestLspServerAsync(new string[] { markup }, Array.Empty(), LanguageNames.CSharp, clientCapabilities); - - private protected Task CreateVisualBasicTestLspServerAsync(string markup, LSP.ClientCapabilities? clientCapabilities = null, WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer) - => CreateTestLspServerAsync(new string[] { markup }, Array.Empty(), LanguageNames.VisualBasic, clientCapabilities, serverKind); - - protected Task CreateMultiProjectLspServerAsync(string xmlMarkup, LSP.ClientCapabilities? clientCapabilities = null) - => CreateTestLspServerAsync(TestWorkspace.Create(xmlMarkup, composition: Composition), clientCapabilities, WellKnownLspServerKinds.AlwaysActiveVSLspServer); + private protected Task CreateTestLspServerAsync(string markup, LSP.ClientCapabilities clientCapabilities) + => CreateTestLspServerAsync(new string[] { markup }, LanguageNames.CSharp, new InitializationOptions { ClientCapabilities = clientCapabilities }); - /// - /// Creates an LSP server backed by a workspace instance with a solution containing the specified documents. - /// - protected Task CreateTestLspServerAsync(string[] markups, LSP.ClientCapabilities? clientCapabilities = null) - => CreateTestLspServerAsync(markups, Array.Empty(), LanguageNames.CSharp, clientCapabilities); + private protected Task CreateTestLspServerAsync(string markup, InitializationOptions? initializationOptions = null) + => CreateTestLspServerAsync(new string[] { markup }, LanguageNames.CSharp, initializationOptions); - private protected Task CreateTestLspServerAsync(string markup, LSP.ClientCapabilities clientCapabilities, WellKnownLspServerKinds serverKind) - => CreateTestLspServerAsync(new string[] { markup }, Array.Empty(), LanguageNames.CSharp, clientCapabilities, serverKind); + private protected Task CreateTestLspServerAsync(string[] markups, InitializationOptions? initializationOptions = null) + => CreateTestLspServerAsync(markups, LanguageNames.CSharp, initializationOptions); - /// - /// Creates an LSP server backed by a workspace instance with a solution containing the specified documents. - /// - protected Task CreateTestLspServerAsync(string[] markups, string[] sourceGeneratedMarkups, LSP.ClientCapabilities? clientCapabilities = null) - => CreateTestLspServerAsync(markups, sourceGeneratedMarkups, LanguageNames.CSharp, clientCapabilities); + private protected Task CreateVisualBasicTestLspServerAsync(string markup, InitializationOptions? initializationOptions = null) + => CreateTestLspServerAsync(new string[] { markup }, LanguageNames.VisualBasic, initializationOptions); - private Task CreateTestLspServerAsync(string[] markups, string[] sourceGeneratedMarkups, string languageName, LSP.ClientCapabilities? clientCapabilities, WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer) + private Task CreateTestLspServerAsync(string[] markups, string languageName, InitializationOptions? initializationOptions) { + var lspOptions = initializationOptions ?? new InitializationOptions(); var exportProvider = Composition.ExportProviderFactory.CreateExportProvider(); var workspaceConfigurationService = exportProvider.GetExportedValue(); workspaceConfigurationService.Options = new WorkspaceConfigurationOptions(EnableOpeningSourceGeneratedFiles: true); + if (lspOptions.OptionUpdater != null) + { + var globalOptions = exportProvider.GetExportedValue(); + lspOptions.OptionUpdater(globalOptions); + } + var workspace = languageName switch { - LanguageNames.CSharp => TestWorkspace.CreateCSharp(markups, sourceGeneratedMarkups, exportProvider: exportProvider), - LanguageNames.VisualBasic => TestWorkspace.CreateVisualBasic(markups, sourceGeneratedMarkups, exportProvider: exportProvider), + LanguageNames.CSharp => TestWorkspace.CreateCSharp(markups, lspOptions.SourceGeneratedMarkups, exportProvider: exportProvider), + LanguageNames.VisualBasic => TestWorkspace.CreateVisualBasic(markups, lspOptions.SourceGeneratedMarkups, exportProvider: exportProvider), _ => throw new ArgumentException($"language name {languageName} is not valid for a test workspace"), }; - return CreateTestLspServerAsync(workspace, clientCapabilities, serverKind); + return CreateTestLspServerAsync(workspace, lspOptions); } - private static async Task CreateTestLspServerAsync(TestWorkspace workspace, LSP.ClientCapabilities? clientCapabilities, WellKnownLspServerKinds serverKind) + private async Task CreateTestLspServerAsync(TestWorkspace workspace, InitializationOptions initializationOptions) { var solution = workspace.CurrentSolution; @@ -346,6 +341,7 @@ private static async Task CreateTestLspServerAsync(TestWorkspace solution = solution.WithProjectFilePath(project.Id, GetDocumentFilePathFromName(project.FilePath)); } + solution = solution.WithAnalyzerReferences(new[] { TestAnalyzerReferences }); workspace.ChangeSolution(solution); // Important: We must wait for workspace creation operations to finish. @@ -353,22 +349,30 @@ private static async Task CreateTestLspServerAsync(TestWorkspace // created by the initial test steps. This can interfere with the expected test state. await WaitForWorkspaceOperationsAsync(workspace); - return await TestLspServer.CreateAsync(workspace, clientCapabilities ?? new LSP.ClientCapabilities(), serverKind); + return await TestLspServer.CreateAsync(workspace, initializationOptions); } private protected async Task CreateXmlTestLspServerAsync( string xmlContent, string? workspaceKind = null, - LSP.ClientCapabilities? clientCapabilities = null, - WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer) + InitializationOptions? initializationOptions = null) { - var workspace = TestWorkspace.Create(XElement.Parse(xmlContent), openDocuments: false, composition: Composition, workspaceKind: workspaceKind); + var lspOptions = initializationOptions ?? new InitializationOptions(); + var exportProvider = Composition.ExportProviderFactory.CreateExportProvider(); + if (lspOptions.OptionUpdater != null) + { + var globalOptions = exportProvider.GetExportedValue(); + lspOptions.OptionUpdater(globalOptions); + } + + var workspace = TestWorkspace.Create(XElement.Parse(xmlContent), openDocuments: false, exportProvider: exportProvider, workspaceKind: workspaceKind); + workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { TestAnalyzerReferences })); // Important: We must wait for workspace creation operations to finish. // Otherwise we could have a race where workspace change events triggered by creation are changing the state // created by the initial test steps. This can interfere with the expected test state. await WaitForWorkspaceOperationsAsync(workspace); - return await TestLspServer.CreateAsync(workspace, clientCapabilities ?? new LSP.ClientCapabilities(), serverKind); + return await TestLspServer.CreateAsync(workspace, lspOptions); } /// @@ -472,7 +476,7 @@ private static LSP.DidCloseTextDocumentParams CreateDidCloseTextDocumentParams(U } }; - public sealed class TestLspServer : IDisposable + internal sealed class TestLspServer : IDisposable { public readonly TestWorkspace TestWorkspace; private readonly Dictionary> _locations; @@ -534,14 +538,14 @@ private static JsonMessageFormatter CreateJsonMessageFormatter() return messageFormatter; } - internal static async Task CreateAsync(TestWorkspace testWorkspace, LSP.ClientCapabilities clientCapabilities, WellKnownLspServerKinds serverKind) + internal static async Task CreateAsync(TestWorkspace testWorkspace, InitializationOptions initializationOptions) { var locations = await GetAnnotatedLocationsAsync(testWorkspace, testWorkspace.CurrentSolution); - var server = new TestLspServer(testWorkspace, locations, clientCapabilities, serverKind); + var server = new TestLspServer(testWorkspace, locations, initializationOptions.ClientCapabilities, initializationOptions.ServerKind); await server.ExecuteRequestAsync(LSP.Methods.InitializeName, new LSP.InitializeParams { - Capabilities = clientCapabilities, + Capabilities = initializationOptions.ClientCapabilities, }, CancellationToken.None); return server; @@ -639,22 +643,6 @@ public Task CloseDocumentAsync(Uri documentUri) public Solution GetCurrentSolution() => TestWorkspace.CurrentSolution; - internal void InitializeDiagnostics(BackgroundAnalysisScope scope, DiagnosticMode diagnosticMode, TestAnalyzerReferenceByLanguage references) - { - TestWorkspace.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), scope); - TestWorkspace.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic), scope); - TestWorkspace.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, InternalLanguageNames.TypeScript), scope); - TestWorkspace.GlobalOptions.SetGlobalOption(new OptionKey(InternalDiagnosticsOptions.NormalDiagnosticMode), diagnosticMode); - - TestWorkspace.TryApplyChanges(TestWorkspace.CurrentSolution.WithAnalyzerReferences(new[] { references })); - - var registrationService = TestWorkspace.Services.GetRequiredService(); - registrationService.Register(TestWorkspace); - - var diagnosticService = (DiagnosticService)TestWorkspace.ExportProvider.GetExportedValue(); - diagnosticService.Register(new TestHostDiagnosticUpdateSource(TestWorkspace)); - } - internal async Task WaitForDiagnosticsAsync() { var listenerProvider = TestWorkspace.GetService(); diff --git a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs index 90f2a59eece6a..7bebe2e83649e 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection.Metadata; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindUsages; @@ -36,8 +37,8 @@ public static ImmutableArray GetDocuments(this Solution solution, Uri { var documentIds = GetDocumentIds(solution, documentUri); - var documents = documentIds.SelectAsArray(id => solution.GetRequiredDocument(id)); - + // We don't call GetRequiredDocument here as the id could be referring to an additional document. + var documents = documentIds.Select(solution.GetDocument).WhereNotNull().ToImmutableArray(); return documents; } @@ -103,6 +104,14 @@ public static Document FindDocumentInProjectContext(this ImmutableArray solution.Projects.Where(project => project.FilePath == projectIdentifier.Uri.LocalPath).SingleOrDefault(); + public static TextDocument? GetAdditionalDocument(this Solution solution, TextDocumentIdentifier documentIdentifier) + { + var documentIds = GetDocumentIds(solution, documentIdentifier.Uri); + + // We don't call GetRequiredAdditionalDocument as the id could be referring to a regular document. + return documentIds.Select(solution.GetAdditionalDocument).WhereNotNull().SingleOrDefault(); + } + public static async Task GetPositionFromLinePositionAsync(this TextDocument document, LinePosition linePosition, CancellationToken cancellationToken) { var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index c5cc1dcec2493..f0ec470cf56cb 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -89,10 +89,19 @@ protected AbstractPullDiagnosticHandler( protected abstract ValueTask> GetOrderedDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); /// - /// Creates the instance we'll report back to clients to let them know our - /// progress. Subclasses can fill in data specific to their needs as appropriate. + /// Creates the appropriate LSP type to report a new set of diagnostics and resultId. /// - protected abstract TReport CreateReport(TextDocumentIdentifier identifier, LSP.Diagnostic[]? diagnostics, string? resultId); + protected abstract TReport CreateReport(TextDocumentIdentifier identifier, LSP.Diagnostic[] diagnostics, string resultId); + + /// + /// Creates the appropriate LSP type to report unchanged diagnostics. + /// + protected abstract TReport CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId); + + /// + /// Creates the appropriate LSP type to report a removed file. + /// + protected abstract TReport CreateRemovedReport(TextDocumentIdentifier identifier); protected abstract TReturn? CreateReturn(BufferedProgress progress); @@ -157,7 +166,7 @@ protected AbstractPullDiagnosticHandler( // same-result-id) response to the client as that means they should just preserve the current // diagnostics they have for this file. var previousParams = documentToPreviousDiagnosticParams[diagnosticSource.GetId()]; - progress.Report(CreateReport(previousParams.TextDocument, diagnostics: null, previousParams.PreviousResultId)); + progress.Report(CreateUnchangedReport(previousParams.TextDocument, previousParams.PreviousResultId)); } } @@ -209,6 +218,12 @@ private static Dictionary GetIdToPrevio return new ProjectOrDocumentId(project.Id); } + var additionalDocument = solution.GetAdditionalDocument(textDocumentIdentifier); + if (additionalDocument != null) + { + return new ProjectOrDocumentId(additionalDocument.Id); + } + return null; } } @@ -254,7 +269,7 @@ private void HandleRemovedDocuments(RequestContext context, ImmutableArray CreateReport(identifier, diagnostics: null, resultId: null); + + protected override VSInternalDiagnosticReport CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId) + => CreateReport(identifier, diagnostics: null, resultId); + protected override ImmutableArray? GetPreviousResults(VSInternalDocumentDiagnosticsParams diagnosticsParams) { if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs index 36094e977023f..09e8281c94063 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs @@ -42,14 +42,14 @@ protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData) return ConvertTags(diagnosticData, potentialDuplicate: false); } - protected override DocumentDiagnosticPartialReport CreateReport(TextDocumentIdentifier identifier, VisualStudio.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) - { - // We will only report once for document pull, so we only need to return the first literal send = DocumentDiagnosticReport. - var report = diagnostics == null - ? new DocumentDiagnosticReport(new UnchangedDocumentDiagnosticReport(resultId)) - : new DocumentDiagnosticReport(new FullDocumentDiagnosticReport(resultId, diagnostics)); - return report; - } + protected override DocumentDiagnosticPartialReport CreateReport(TextDocumentIdentifier identifier, VisualStudio.LanguageServer.Protocol.Diagnostic[] diagnostics, string resultId) + => new DocumentDiagnosticReport(new FullDocumentDiagnosticReport(resultId, diagnostics)); + + protected override DocumentDiagnosticPartialReport CreateRemovedReport(TextDocumentIdentifier identifier) + => new DocumentDiagnosticReport(new FullDocumentDiagnosticReport(resultId: null, Array.Empty())); + + protected override DocumentDiagnosticPartialReport CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId) + => new DocumentDiagnosticReport(new UnchangedDocumentDiagnosticReport(resultId)); protected override DocumentDiagnosticReport? CreateReturn(BufferedProgress progress) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs index 468405df55314..9c9492fddcfa9 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs @@ -37,13 +37,23 @@ protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData) return ConvertTags(diagnosticData, potentialDuplicate: false); } - protected override WorkspaceDiagnosticReport CreateReport(TextDocumentIdentifier identifier, VisualStudio.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) - { - var itemToReport = diagnostics == null - ? new WorkspaceDocumentDiagnosticReport(new WorkspaceUnchangedDocumentDiagnosticReport(identifier.Uri, resultId, version: null)) - : new WorkspaceDocumentDiagnosticReport(new WorkspaceFullDocumentDiagnosticReport(identifier.Uri, diagnostics, version: null, resultId)); - return new WorkspaceDiagnosticReport(new[] { itemToReport }); - } + protected override WorkspaceDiagnosticReport CreateReport(TextDocumentIdentifier identifier, VisualStudio.LanguageServer.Protocol.Diagnostic[] diagnostics, string resultId) + => new WorkspaceDiagnosticReport(new[] + { + new WorkspaceDocumentDiagnosticReport(new WorkspaceFullDocumentDiagnosticReport(identifier.Uri, diagnostics, version: null, resultId)) + }); + + protected override WorkspaceDiagnosticReport CreateRemovedReport(TextDocumentIdentifier identifier) + => new WorkspaceDiagnosticReport(new[] + { + new WorkspaceDocumentDiagnosticReport(new WorkspaceFullDocumentDiagnosticReport(identifier.Uri, Array.Empty(), version: null, resultId: null)) + }); + + protected override WorkspaceDiagnosticReport CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId) + => new WorkspaceDiagnosticReport(new[] + { + new WorkspaceDocumentDiagnosticReport(new WorkspaceUnchangedDocumentDiagnosticReport(identifier.Uri, resultId, version: null)) + }); protected override WorkspaceDiagnosticReport? CreateReturn(BufferedProgress progress) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index dc93d10e05e1a..a7c3a714d8e5e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -49,6 +49,12 @@ protected override VSInternalWorkspaceDiagnosticReport CreateReport(TextDocument Identifier = WorkspaceDiagnosticIdentifier, }; + protected override VSInternalWorkspaceDiagnosticReport CreateRemovedReport(TextDocumentIdentifier identifier) + => CreateReport(identifier, diagnostics: null, resultId: null); + + protected override VSInternalWorkspaceDiagnosticReport CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId) + => CreateReport(identifier, diagnostics: null, resultId); + protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray(); @@ -118,11 +124,11 @@ async Task AddDocumentsAndProject(Project? project, ImmutableArray suppo } var isFSAOn = globalOptions.IsFullSolutionAnalysisEnabled(project.Language); - var documents = ImmutableArray.Empty; + var documents = ImmutableArray.Empty; // If FSA is on, then add all the documents in the project. Other analysis scopes are handled by the document pull handler. if (isFSAOn) { - documents = documents.AddRange(project.Documents); + documents = documents.AddRange(project.Documents).AddRange(project.AdditionalDocuments); } // If all features are enabled for source generated documents, make sure they are included when FSA is on or a file in the project is open. @@ -188,7 +194,7 @@ public async Task> GetDiagnosticsAsync( } } - private record struct WorkspaceDocumentDiagnosticSource(Document Document) : IDiagnosticSource + private record struct WorkspaceDocumentDiagnosticSource(TextDocument Document) : IDiagnosticSource { public ProjectOrDocumentId GetId() => new(Document.Id); @@ -202,18 +208,18 @@ public async Task> GetDiagnosticsAsync( DiagnosticMode diagnosticMode, CancellationToken cancellationToken) { - if (Document is not SourceGeneratedDocument) + if (Document is SourceGeneratedDocument sourceGeneratedDocument) { - // We call GetDiagnosticsForIdsAsync as we want to ensure we get the full set of diagnostics for this document - // including those reported as a compilation end diagnostic. These are not included in document pull (uses GetDiagnosticsForSpan) due to cost. - // However we can include them as a part of workspace pull when FSA is on. - var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync(Document.Project.Solution, Document.Project.Id, Document.Id, cancellationToken: cancellationToken).ConfigureAwait(false); + // Unfortunately GetDiagnosticsForIdsAsync returns nothing for source generated documents. + var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync(sourceGeneratedDocument, range: null, cancellationToken: cancellationToken).ConfigureAwait(false); return documentDiagnostics; } else { - // Unfortunately GetDiagnosticsForIdsAsync returns nothing for source generated documents. - var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync(Document, range: null, cancellationToken: cancellationToken).ConfigureAwait(false); + // We call GetDiagnosticsForIdsAsync as we want to ensure we get the full set of diagnostics for this document + // including those reported as a compilation end diagnostic. These are not included in document pull (uses GetDiagnosticsForSpan) due to cost. + // However we can include them as a part of workspace pull when FSA is on. + var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync(Document.Project.Solution, Document.Project.Id, Document.Id, cancellationToken: cancellationToken).ConfigureAwait(false); return documentDiagnostics; } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs index 4ce1225326c69..956fcd414e586 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs @@ -75,7 +75,7 @@ void M() int {|caret:|}i = 1; } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var caretLocation = testLspServer.GetLocations("caret").Single(); var expected = CreateCodeAction( @@ -86,14 +86,17 @@ void M() FeaturesResources.Introduce_constant + '|' + string.Format(FeaturesResources.Introduce_constant_for_0, "1"), caretLocation), priority: VSInternalPriorityLevel.Normal, - groupName: "Roslyn2", + groupName: "Roslyn3", applicableRange: new LSP.Range { Start = new Position { Line = 4, Character = 12 }, End = new Position { Line = 4, Character = 12 } }, diagnostics: null); + var results = await RunGetCodeActionsAsync(testLspServer, CreateCodeActionParams(caretLocation)); - var introduceConstant = results[0].Children.FirstOrDefault( - r => ((JObject)r.Data).ToObject().UniqueIdentifier == FeaturesResources.Introduce_constant - + '|' + string.Format(FeaturesResources.Introduce_constant_for_0, "1")); + + var topLevelAction = Assert.Single(results.Where(action => action.Title == FeaturesResources.Introduce_constant)); + var expectedChildActionTitle = FeaturesResources.Introduce_constant + '|' + string.Format(FeaturesResources.Introduce_constant_for_0, "1"); + var introduceConstant = topLevelAction.Children.FirstOrDefault( + r => ((JObject)r.Data).ToObject().UniqueIdentifier == expectedChildActionTitle); AssertJsonEquals(expected, introduceConstant); } @@ -201,10 +204,6 @@ void M() }"; using var testLspServer = await CreateTestLspServerAsync(markup); - testLspServer.InitializeDiagnostics(BackgroundAnalysisScope.ActiveFile, DiagnosticMode.Default, - new TestAnalyzerReferenceByLanguage(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap())); - await testLspServer.WaitForDiagnosticsAsync(); - var caret = testLspServer.GetLocations("caret").Single(); var codeActionParams = new LSP.CodeActionParams { diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs index 5273013540e74..8cfc336fe976f 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs @@ -125,23 +125,23 @@ public async Task TestGotoDefinitionCrossLanguage() { var markup = @" - - + + public class {|definition:A|} { } - + Definition - + Class C Dim a As {|caret:A|} End Class "; - using var testLspServer = await CreateMultiProjectLspServerAsync(markup); + using var testLspServer = await CreateXmlTestLspServerAsync(markup); var results = await RunGotoDefinitionAsync(testLspServer, testLspServer.GetLocations("caret").Single()); AssertLocationsEqual(testLspServer.GetLocations("definition"), results); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs index db4cd2b93c0ee..dba7b0c11c168 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs @@ -9,8 +9,10 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Experimental; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; @@ -27,8 +29,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Diagnostics public abstract class AbstractPullDiagnosticTestsBase : AbstractLanguageServerProtocolTests { - protected virtual ImmutableDictionary> TestAnalyzers => DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap() - .Add(InternalLanguageNames.TypeScript, ImmutableArray.Create(new MockTypescriptDiagnosticAnalyzer())); + private protected override TestAnalyzerReferenceByLanguage TestAnalyzerReferences => new(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap() + .Add(InternalLanguageNames.TypeScript, ImmutableArray.Create(new MockTypescriptDiagnosticAnalyzer()))); protected override TestComposition Composition => base.Composition.AddParts(typeof(MockTypescriptDiagnosticAnalyzer)); @@ -210,36 +212,34 @@ static DocumentDiagnosticParams CreateProposedDocumentDiagnosticParams( } private protected Task CreateTestWorkspaceWithDiagnosticsAsync(string markup, BackgroundAnalysisScope scope, bool useVSDiagnostics, bool pullDiagnostics = true) - => CreateTestWorkspaceWithDiagnosticsAsync(markup, scope, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push, useVSDiagnostics); - - private protected async Task CreateTestWorkspaceFromXmlAsync(string xmlMarkup, BackgroundAnalysisScope scope, bool useVSDiagnostics, bool pullDiagnostics = true) - { - var testLspServer = await CreateXmlTestLspServerAsync(xmlMarkup, clientCapabilities: useVSDiagnostics ? CapabilitiesWithVSExtensions : new LSP.ClientCapabilities()); - InitializeDiagnostics(scope, testLspServer, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push); - return testLspServer; - } - - private protected async Task CreateTestWorkspaceWithDiagnosticsAsync(string markup, BackgroundAnalysisScope scope, DiagnosticMode mode, bool useVSDiagnostics, WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer) - { - var testLspServer = await CreateTestLspServerAsync(markup, useVSDiagnostics ? CapabilitiesWithVSExtensions : new LSP.ClientCapabilities(), serverKind); - InitializeDiagnostics(scope, testLspServer, mode); - return testLspServer; - } + => CreateTestLspServerAsync(markup, GetInitializationOptions(scope, useVSDiagnostics, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push)); private protected Task CreateTestWorkspaceWithDiagnosticsAsync(string[] markups, BackgroundAnalysisScope scope, bool useVSDiagnostics, bool pullDiagnostics = true) - => CreateTestWorkspaceWithDiagnosticsAsync(markups, Array.Empty(), scope, useVSDiagnostics, pullDiagnostics); + => CreateTestLspServerAsync(markups, GetInitializationOptions(scope, useVSDiagnostics, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push)); - private protected async Task CreateTestWorkspaceWithDiagnosticsAsync(string[] markups, string[] sourceGeneratedMarkups, BackgroundAnalysisScope scope, bool useVSDiagnostics, bool pullDiagnostics = true) - { - var testLspServer = await CreateTestLspServerAsync(markups, sourceGeneratedMarkups, useVSDiagnostics ? CapabilitiesWithVSExtensions : new LSP.ClientCapabilities()); - InitializeDiagnostics(scope, testLspServer, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push); - return testLspServer; - } + private protected Task CreateTestWorkspaceFromXmlAsync(string xmlMarkup, BackgroundAnalysisScope scope, bool useVSDiagnostics, bool pullDiagnostics = true) + => CreateXmlTestLspServerAsync(xmlMarkup, initializationOptions: GetInitializationOptions(scope, useVSDiagnostics, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push)); - private void InitializeDiagnostics(BackgroundAnalysisScope scope, TestLspServer testLspServer, DiagnosticMode diagnosticMode) + private protected static InitializationOptions GetInitializationOptions( + BackgroundAnalysisScope scope, + bool useVSDiagnostics, + DiagnosticMode mode, + WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer, + string[]? sourceGeneratedMarkups = null) { - var analyzerReference = new TestAnalyzerReferenceByLanguage(TestAnalyzers); - testLspServer.InitializeDiagnostics(scope, diagnosticMode, analyzerReference); + return new InitializationOptions + { + ClientCapabilities = useVSDiagnostics ? CapabilitiesWithVSExtensions : new LSP.ClientCapabilities(), + OptionUpdater = (globalOptions) => + { + globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), scope); + globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic), scope); + globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, InternalLanguageNames.TypeScript), scope); + globalOptions.SetGlobalOption(new OptionKey(InternalDiagnosticsOptions.NormalDiagnosticMode), mode); + }, + ServerKind = serverKind, + SourceGeneratedMarkups = sourceGeneratedMarkups ?? Array.Empty() + }; } /// diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs new file mode 100644 index 0000000000000..1fdaf6583bbae --- /dev/null +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs @@ -0,0 +1,113 @@ +// 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.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.SolutionCrawler; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; +using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Diagnostics; +public class AdditionalFileDiagnosticsTests : AbstractPullDiagnosticTestsBase +{ + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsReportsAdditionalFileDiagnostic(bool useVSDiagnostics) + { + var workspaceXml = +@$" + + + + +"; + + using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + Assert.Equal(3, results.Length); + + Assert.Empty(results[0].Diagnostics); + Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[1].Diagnostics.Single().Code); + Assert.Equal(@"C:\Test.txt", results[1].Uri.LocalPath); + Assert.Empty(results[2].Diagnostics); + + // Asking again should give us back an unchanged diagnostic. + var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); + Assert.Null(results2[0].Diagnostics); + Assert.Null(results2[1].Diagnostics); + Assert.Equal(results[1].ResultId, results2[1].ResultId); + Assert.Null(results2[2].Diagnostics); + } + + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsWithRemovedAdditionalFile(bool useVSDiagnostics) + { + var workspaceXml = +@$" + + + + +"; + + using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + Assert.Equal(3, results.Length); + + Assert.Empty(results[0].Diagnostics); + Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[1].Diagnostics.Single().Code); + Assert.Equal(@"C:\Test.txt", results[1].Uri.LocalPath); + Assert.Empty(results[2].Diagnostics); + + var initialSolution = testLspServer.GetCurrentSolution(); + var newSolution = initialSolution.RemoveAdditionalDocument(initialSolution.Projects.Single().AdditionalDocumentIds.Single()); + await testLspServer.TestWorkspace.ChangeSolutionAsync(newSolution); + + var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); + Assert.Equal(3, results2.Length); + + // The first report is the report for the removed additional file. + Assert.Equal(useVSDiagnostics ? null : Array.Empty(), results2[0].Diagnostics); + Assert.Null(results2[0].ResultId); + + // The other files should have new results since the solution changed. + Assert.Empty(results2[1].Diagnostics); + Assert.NotNull(results2[1].ResultId); + Assert.Empty(results2[2].Diagnostics); + Assert.NotNull(results2[2].ResultId); + } + + protected override TestComposition Composition => base.Composition.AddParts(typeof(MockAdditionalFileDiagnosticAnalyzer)); + + private protected override TestAnalyzerReferenceByLanguage TestAnalyzerReferences => new(ImmutableDictionary.Create>() + .Add(LanguageNames.CSharp, ImmutableArray.Create(DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp), new MockAdditionalFileDiagnosticAnalyzer()))); + + [DiagnosticAnalyzer(LanguageNames.CSharp), PartNotDiscoverable] + private class MockAdditionalFileDiagnosticAnalyzer : DiagnosticAnalyzer + { + public const string Id = "MockAdditionalDiagnostic"; + private readonly DiagnosticDescriptor _descriptor = new(Id, "MockAdditionalDiagnostic", "MockAdditionalDiagnostic", "InternalCategory", DiagnosticSeverity.Warning, isEnabledByDefault: true, helpLinkUri: "https://github.com/dotnet/roslyn"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(_descriptor); + + public override void Initialize(AnalysisContext context) + => context.RegisterCompilationStartAction(CreateAnalyzerWithinCompilation); + + public void CreateAnalyzerWithinCompilation(CompilationStartAnalysisContext context) + => context.RegisterAdditionalFileAction(AnalyzeCompilation); + + public void AnalyzeCompilation(AdditionalFileAnalysisContext context) + => context.ReportDiagnostic(Diagnostic.Create(_descriptor, + location: Location.Create(context.AdditionalFile.Path, Text.TextSpan.FromBounds(0, 0), new Text.LinePositionSpan(new Text.LinePosition(0, 0), new Text.LinePosition(0, 0))), "args")); + } +} diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs index e4e8fc02075b6..4669c384bf3b3 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs @@ -90,7 +90,8 @@ public async Task TestNoDocumentDiagnosticsForOpenFilesIfDefaultAndFeatureFlagOf { var markup = @"class A {"; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Default, useVSDiagnostics); + using var testLspServer = await CreateTestLspServerAsync(markup, + GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, useVSDiagnostics, DiagnosticMode.Default)); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -108,7 +109,8 @@ public async Task TestDocumentDiagnosticsForOpenFilesIfDefaultAndFeatureFlagOn(b { var markup = @"class A {"; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Default, useVSDiagnostics); + using var testLspServer = await CreateTestLspServerAsync(markup, + GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, useVSDiagnostics, DiagnosticMode.Default)); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -149,7 +151,7 @@ public async Task TestDocumentDiagnosticsForRemovedDocument(bool useVSDiagnostic results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics, results.Single().ResultId).ConfigureAwait(false); - Assert.Null(results.Single().Diagnostics); + Assert.Equal(useVSDiagnostics ? null : Array.Empty(), results.Single().Diagnostics); Assert.Null(results.Single().ResultId); } @@ -458,7 +460,8 @@ public async Task TestDocumentDiagnosticsFromRazorServer(bool useVSDiagnostics) @"class A {"; // Turn off pull diagnostics by default, but send a request to the razor LSP server which is always pull. - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Push, useVSDiagnostics, serverKind: WellKnownLspServerKinds.RazorLspServer); + using var testLspServer = await CreateTestLspServerAsync(markup, + GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, useVSDiagnostics, DiagnosticMode.Push, WellKnownLspServerKinds.RazorLspServer)); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -482,7 +485,8 @@ public async Task TestDocumentDiagnosticsFromLiveShareServer(bool useVSDiagnosti @"class A {"; // Turn off pull diagnostics by default, but send a request to the razor LSP server which is always pull. - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Push, useVSDiagnostics, serverKind: WellKnownLspServerKinds.LiveShareLspServer); + using var testLspServer = await CreateTestLspServerAsync(markup, + GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, useVSDiagnostics, DiagnosticMode.Push, WellKnownLspServerKinds.LiveShareLspServer)); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -658,11 +662,9 @@ public async Task TestWorkspaceDiagnosticsForSourceGeneratedFiles(bool useVSDiag var markup1 = @"class A {"; var markup2 = ""; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + using var testLspServer = await CreateTestLspServerAsync( markups: Array.Empty(), - sourceGeneratedMarkups: new[] { markup1, markup2 }, - BackgroundAnalysisScope.FullSolution, - useVSDiagnostics); + GetInitializationOptions(BackgroundAnalysisScope.FullSolution, useVSDiagnostics, DiagnosticMode.Pull, sourceGeneratedMarkups: new[] { markup1, markup2 })); var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); @@ -700,7 +702,7 @@ public async Task TestWorkspaceDiagnosticsForRemovedDocument(bool useVSDiagnosti // First doc should show up as removed. Assert.Equal(3, results2.Length); - Assert.Null(results2[0].Diagnostics); + Assert.Equal(useVSDiagnostics ? null : Array.Empty(), results2[0].Diagnostics); Assert.Null(results2[0].ResultId); // Second and third doc should be changed as the project has changed. diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs index c1c28061b6042..9a10419dedf31 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs @@ -12,8 +12,8 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.VisualStudio.LanguageServer.Protocol; using Xunit; +using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Diagnostics; public class WorkspaceProjectDiagnosticsTests : AbstractPullDiagnosticTestsBase @@ -55,17 +55,16 @@ public async Task TestWorkspaceDiagnosticsWithRemovedProject(bool useVSDiagnosti var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); Assert.Equal(2, results2.Length); - Assert.Null(results2[0].Diagnostics); + Assert.Equal(useVSDiagnostics ? null : Array.Empty(), results2[0].Diagnostics); Assert.Null(results2[0].ResultId); - Assert.Null(results2[1].Diagnostics); + Assert.Equal(useVSDiagnostics ? null : Array.Empty(), results2[1].Diagnostics); Assert.Null(results2[1].ResultId); } protected override TestComposition Composition => base.Composition.AddParts(typeof(MockProjectDiagnosticAnalyzer)); - protected override ImmutableDictionary> TestAnalyzers - => ImmutableDictionary.Create>() - .Add(LanguageNames.CSharp, ImmutableArray.Create(DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp), new MockProjectDiagnosticAnalyzer())); + private protected override TestAnalyzerReferenceByLanguage TestAnalyzerReferences => new(ImmutableDictionary.Create>() + .Add(LanguageNames.CSharp, ImmutableArray.Create(DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp), new MockProjectDiagnosticAnalyzer()))); [DiagnosticAnalyzer(LanguageNames.CSharp), PartNotDiscoverable] private class MockProjectDiagnosticAnalyzer : DiagnosticAnalyzer diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs index c2fa4a2157de5..9127740b2806d 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs @@ -179,7 +179,7 @@ static void Main(string[] args) "; - using var testLspServer = await CreateXmlTestLspServerAsync(workspaceXml, clientCapabilities: CapabilitiesWithVSExtensions); + using var testLspServer = await CreateXmlTestLspServerAsync(workspaceXml, initializationOptions: new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions }); var location = testLspServer.GetLocations("caret").Single(); foreach (var project in testLspServer.GetCurrentSolution().Projects) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs index 751e7952e0c06..1ad0348068637 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs @@ -29,7 +29,7 @@ void M() }"; // Create a server that supports LSP misc files and verify no misc files present. - using var testLspServer = await CreateTestLspServerAsync(string.Empty, new LSP.ClientCapabilities(), WellKnownLspServerKinds.CSharpVisualBasicLspServer); + using var testLspServer = await CreateTestLspServerAsync(string.Empty, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); Assert.Null(GetMiscellaneousDocument(testLspServer)); // Open an empty loose file and make a request to verify it gets added to the misc workspace. @@ -52,7 +52,7 @@ void M() }"; // Create a server that supports LSP misc files and verify no misc files present. - using var testLspServer = await CreateTestLspServerAsync(string.Empty, new LSP.ClientCapabilities(), WellKnownLspServerKinds.CSharpVisualBasicLspServer); + using var testLspServer = await CreateTestLspServerAsync(string.Empty, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); Assert.Null(GetMiscellaneousDocument(testLspServer)); var looseFileUri = new Uri(@"C:\SomeFile.cs"); @@ -81,7 +81,7 @@ void M() }"; // Create a server that supports LSP misc files and verify no misc files present. - using var testLspServer = await CreateTestLspServerAsync(string.Empty, new LSP.ClientCapabilities(), WellKnownLspServerKinds.CSharpVisualBasicLspServer); + using var testLspServer = await CreateTestLspServerAsync(string.Empty, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); Assert.Null(GetMiscellaneousDocument(testLspServer)); // Open an empty loose file and make a request to verify it gets added to the misc workspace. @@ -106,7 +106,7 @@ void M() }"; // Create a server that supports LSP misc files and verify no misc files present. - using var testLspServer = await CreateTestLspServerAsync(markup, new LSP.ClientCapabilities(), WellKnownLspServerKinds.CSharpVisualBasicLspServer); + using var testLspServer = await CreateTestLspServerAsync(markup, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); Assert.Null(GetMiscellaneousDocument(testLspServer)); // Open a file that is part of a registered workspace and verify it is not present in the misc workspace. @@ -127,7 +127,7 @@ void M() }"; // Create a server that supports LSP misc files and verify no misc files present. - using var testLspServer = await CreateTestLspServerAsync(string.Empty, new LSP.ClientCapabilities(), WellKnownLspServerKinds.CSharpVisualBasicLspServer); + using var testLspServer = await CreateTestLspServerAsync(string.Empty, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); Assert.Null(GetMiscellaneousDocument(testLspServer)); // Open an empty loose file and make a request to verify it gets added to the misc workspace. @@ -167,7 +167,7 @@ void M() }"; // Create a server that doesn't use the LSP misc files workspace. - using var testLspServer = await CreateTestLspServerAsync(string.Empty, new LSP.ClientCapabilities(), WellKnownLspServerKinds.AlwaysActiveVSLspServer); + using var testLspServer = await CreateTestLspServerAsync(string.Empty, new InitializationOptions { ServerKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer }); Assert.Null(testLspServer.GetManagerAccessor().GetLspMiscellaneousFilesWorkspace()); // Open an empty loose file and make a request to verify it gets added to the misc workspace. diff --git a/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs index 8faf23f9e063b..7fe9c0354eb27 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs @@ -385,11 +385,11 @@ private async Task VerifyMarkupAndExpected( Task testLspServerTask; if (languageName == LanguageNames.CSharp) { - testLspServerTask = CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions, serverKind); + testLspServerTask = CreateTestLspServerAsync(markup, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions, ServerKind = serverKind }); } else if (languageName == LanguageNames.VisualBasic) { - testLspServerTask = CreateVisualBasicTestLspServerAsync(markup, CapabilitiesWithVSExtensions, serverKind); + testLspServerTask = CreateVisualBasicTestLspServerAsync(markup, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions, ServerKind = serverKind }); } else { diff --git a/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs index 88d9d347be81c..71cf34fe4ba58 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs @@ -171,7 +171,7 @@ void M2() }" }; - using var testLspServer = await CreateTestLspServerAsync(markups, CapabilitiesWithVSExtensions); + using var testLspServer = await CreateTestLspServerAsync(markups, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions }); var results = await RunFindAllReferencesAsync(testLspServer, testLspServer.GetLocations("caret").First()); AssertLocationsEqual(testLspServer.GetLocations("reference"), results.Select(result => result.Location)); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs index 6443727f79469..0c376640ebc85 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs @@ -191,7 +191,7 @@ public async Task TestLspTransfersDocumentToNewWorkspaceAsync() var markup = "One"; // Create a server that includes the LSP misc files workspace so we can test transfers to and from it. - using var testLspServer = await CreateTestLspServerAsync(markup, new ClientCapabilities(), serverKind: WellKnownLspServerKinds.CSharpVisualBasicLspServer); + using var testLspServer = await CreateTestLspServerAsync(markup, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); // Create a new document, but do not update the workspace solution yet. var newDocumentId = DocumentId.CreateNewId(testLspServer.TestWorkspace.CurrentSolution.ProjectIds[0]); @@ -433,8 +433,8 @@ public async Task TestSeparateWorkspaceManagerPerServerAsync() var documentUri = testWorkspace.CurrentSolution.Projects.First().Documents.First().GetURI(); - using var testLspServerOne = await TestLspServer.CreateAsync(testWorkspace, clientCapabilities: new(), WellKnownLspServerKinds.AlwaysActiveVSLspServer); - using var testLspServerTwo = await TestLspServer.CreateAsync(testWorkspace, clientCapabilities: new(), WellKnownLspServerKinds.AlwaysActiveVSLspServer); + using var testLspServerOne = await TestLspServer.CreateAsync(testWorkspace, new InitializationOptions()); + using var testLspServerTwo = await TestLspServer.CreateAsync(testWorkspace, new InitializationOptions()); Assert.NotEqual(testLspServerOne.GetManager(), testLspServerTwo.GetManager()); From 40c7839179b49d4a5fbe00487b6020e0c1949652 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Wed, 15 Jun 2022 13:08:11 -0700 Subject: [PATCH 2/3] fix formatting --- .../Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs index 9c9492fddcfa9..b330cd0387676 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs @@ -46,7 +46,7 @@ protected override WorkspaceDiagnosticReport CreateReport(TextDocumentIdentifier protected override WorkspaceDiagnosticReport CreateRemovedReport(TextDocumentIdentifier identifier) => new WorkspaceDiagnosticReport(new[] { - new WorkspaceDocumentDiagnosticReport(new WorkspaceFullDocumentDiagnosticReport(identifier.Uri, Array.Empty(), version: null, resultId: null)) + new WorkspaceDocumentDiagnosticReport(new WorkspaceFullDocumentDiagnosticReport(identifier.Uri, Array.Empty(), version: null, resultId: null)) }); protected override WorkspaceDiagnosticReport CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId) From bfc5a91bbd36d4460b4d239faaa6f7843f08dc97 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Wed, 15 Jun 2022 15:54:22 -0700 Subject: [PATCH 3/3] Fix more formatting --- .../ProtocolUnitTests/CodeActions/CodeActionsTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs index 956fcd414e586..15cab83dbddcf 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs @@ -90,7 +90,6 @@ void M() applicableRange: new LSP.Range { Start = new Position { Line = 4, Character = 12 }, End = new Position { Line = 4, Character = 12 } }, diagnostics: null); - var results = await RunGetCodeActionsAsync(testLspServer, CreateCodeActionParams(caretLocation)); var topLevelAction = Assert.Single(results.Where(action => action.Title == FeaturesResources.Introduce_constant));