diff --git a/src/EditorFeatures/CSharpTest/PdbSourceDocument/AbstractPdbSourceDocumentTests.cs b/src/EditorFeatures/CSharpTest/PdbSourceDocument/AbstractPdbSourceDocumentTests.cs index 439bd53db35c2..978ccf642e208 100644 --- a/src/EditorFeatures/CSharpTest/PdbSourceDocument/AbstractPdbSourceDocumentTests.cs +++ b/src/EditorFeatures/CSharpTest/PdbSourceDocument/AbstractPdbSourceDocumentTests.cs @@ -132,7 +132,7 @@ protected static async Task GenerateFileAndVerifyAsync( try { // Using default settings here because none of the tests exercise any of the settings - var file = await service.GetGeneratedFileAsync(project, symbol, signaturesOnly: false, MetadataAsSourceOptions.GetDefault(project.Services), CancellationToken.None).ConfigureAwait(false); + var file = await service.GetGeneratedFileAsync(workspace, project, symbol, signaturesOnly: false, MetadataAsSourceOptions.GetDefault(project.Services), CancellationToken.None).ConfigureAwait(false); if (expectNullResult) { diff --git a/src/EditorFeatures/CSharpTest/PdbSourceDocument/NullResultMetadataAsSourceFileProvider.cs b/src/EditorFeatures/CSharpTest/PdbSourceDocument/NullResultMetadataAsSourceFileProvider.cs index 825be7e2913f8..efa27a046e766 100644 --- a/src/EditorFeatures/CSharpTest/PdbSourceDocument/NullResultMetadataAsSourceFileProvider.cs +++ b/src/EditorFeatures/CSharpTest/PdbSourceDocument/NullResultMetadataAsSourceFileProvider.cs @@ -36,7 +36,7 @@ public void CleanupGeneratedFiles(Workspace? workspace) { } - public Task GetGeneratedFileAsync(Workspace workspace, Project project, ISymbol symbol, bool signaturesOnly, MetadataAsSourceOptions options, string tempPath, CancellationToken cancellationToken) + public Task GetGeneratedFileAsync(MetadataAsSourceWorkspace metadataWorkspace, Workspace sourceWorkspace, Project sourceProject, ISymbol symbol, bool signaturesOnly, MetadataAsSourceOptions options, string tempPath, CancellationToken cancellationToken) { return Task.FromResult(NullResult); } diff --git a/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs b/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs index 41eec40869823..9f7ad511f8be4 100644 --- a/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs +++ b/src/EditorFeatures/CSharpTest/PdbSourceDocument/PdbSourceDocumentTests.cs @@ -871,7 +871,7 @@ await RunTestAsync(async path => { NavigateToSourceLinkAndEmbeddedSources = false }; - var file = await service.GetGeneratedFileAsync(project, symbol, signaturesOnly: false, options, CancellationToken.None).ConfigureAwait(false); + var file = await service.GetGeneratedFileAsync(workspace, project, symbol, signaturesOnly: false, options, CancellationToken.None).ConfigureAwait(false); Assert.Same(NullResultMetadataAsSourceFileProvider.NullResult, file); } diff --git a/src/EditorFeatures/CSharpTest/TodoComment/NoCompilationTodoCommentTests.cs b/src/EditorFeatures/CSharpTest/TodoComment/NoCompilationTodoCommentTests.cs index d2430d45c6f17..a378b93eec6c0 100644 --- a/src/EditorFeatures/CSharpTest/TodoComment/NoCompilationTodoCommentTests.cs +++ b/src/EditorFeatures/CSharpTest/TodoComment/NoCompilationTodoCommentTests.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Immutable; using System.Composition; using System.Linq; diff --git a/src/EditorFeatures/Core.Wpf/Peek/DefinitionPeekableItem.cs b/src/EditorFeatures/Core.Wpf/Peek/DefinitionPeekableItem.cs index baf73d8360fc4..de62ce70d7679 100644 --- a/src/EditorFeatures/Core.Wpf/Peek/DefinitionPeekableItem.cs +++ b/src/EditorFeatures/Core.Wpf/Peek/DefinitionPeekableItem.cs @@ -78,7 +78,7 @@ public void FindResults(string relationshipName, IPeekResultCollection resultCol { // It's a symbol from metadata, so we want to go produce it from metadata var options = _peekableItem._globalOptions.GetMetadataAsSourceOptions(project.Services); - var declarationFile = _peekableItem._metadataAsSourceFileService.GetGeneratedFileAsync(project, symbol, signaturesOnly: false, options, cancellationToken).WaitAndGetResult_CanCallOnBackground(cancellationToken); + var declarationFile = _peekableItem._metadataAsSourceFileService.GetGeneratedFileAsync(workspace, project, symbol, signaturesOnly: false, options, cancellationToken).WaitAndGetResult_CanCallOnBackground(cancellationToken); var peekDisplayInfo = new PeekResultDisplayInfo(declarationFile.DocumentTitle, declarationFile.DocumentTooltip, declarationFile.DocumentTitle, declarationFile.DocumentTooltip); var identifierSpan = declarationFile.IdentifierLocation.GetLineSpan().Span; var entityOfInterestSpan = PeekHelpers.GetEntityOfInterestSpan(symbol, workspace, declarationFile.IdentifierLocation, cancellationToken); diff --git a/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs b/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs index b908ddfb64a85..e70161c966b3b 100644 --- a/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs +++ b/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs @@ -133,20 +133,18 @@ private async Task UpdateForCaretPositionAsync(SnapshotPoint pointInRoslynSnapsh // be expensive. This doesn't cause a functional issue, since opening the window clears whatever was previously there // so the user won't notice we weren't doing anything when it was open. if (!await _codeDefinitionWindowService.IsWindowOpenAsync(cancellationToken).ConfigureAwait(false)) - { return; - } - var document = pointInRoslynSnapshot.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); - if (document == null) - { + var snapshot = pointInRoslynSnapshot.Snapshot; + var workspace = snapshot.TextBuffer.GetWorkspace(); + var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (workspace is null || document is null) return; - } // Ensure we're off the UI thread for the rest of this since we don't want to be computing locations on the UI thread. await TaskScheduler.Default; - var locations = await GetContextFromPointAsync(document, pointInRoslynSnapshot, cancellationToken).ConfigureAwait(true); + var locations = await GetContextFromPointAsync(workspace, document, pointInRoslynSnapshot, cancellationToken).ConfigureAwait(true); await _codeDefinitionWindowService.SetContextAsync(locations, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) @@ -161,9 +159,8 @@ private async Task UpdateForCaretPositionAsync(SnapshotPoint pointInRoslynSnapsh /// Internal for testing purposes. /// internal async Task> GetContextFromPointAsync( - Document document, int position, CancellationToken cancellationToken) + Workspace workspace, Document document, int position, CancellationToken cancellationToken) { - var workspace = document.Project.Solution.Workspace; var navigableItems = await GoToDefinitionHelpers.GetDefinitionsAsync(document, position, cancellationToken).ConfigureAwait(false); if (navigableItems?.Any() == true) { @@ -211,7 +208,7 @@ internal async Task> GetContextFrom else if (_metadataAsSourceFileService.IsNavigableMetadataSymbol(symbol)) { var options = _globalOptions.GetMetadataAsSourceOptions(document.Project.Services); - var declarationFile = await _metadataAsSourceFileService.GetGeneratedFileAsync(document.Project, symbol, signaturesOnly: false, options, cancellationToken).ConfigureAwait(false); + var declarationFile = await _metadataAsSourceFileService.GetGeneratedFileAsync(workspace, document.Project, symbol, signaturesOnly: false, options, cancellationToken).ConfigureAwait(false); var identifierSpan = declarationFile.IdentifierLocation.GetLineSpan().Span; return ImmutableArray.Create(new CodeDefinitionWindowLocation(symbol.ToDisplayString(), declarationFile.FilePath, identifierSpan.Start)); } diff --git a/src/EditorFeatures/Core/LanguageServer/Handlers/References/FindAllReferencesHandler.cs b/src/EditorFeatures/Core/LanguageServer/Handlers/References/FindAllReferencesHandler.cs index 2f8c906c3c011..105197ea0d092 100644 --- a/src/EditorFeatures/Core/LanguageServer/Handlers/References/FindAllReferencesHandler.cs +++ b/src/EditorFeatures/Core/LanguageServer/Handlers/References/FindAllReferencesHandler.cs @@ -56,7 +56,9 @@ public FindAllReferencesHandler( Debug.Assert(context.ClientCapabilities.HasVisualStudioLspCapability()); var document = context.Document; + var workspace = context.Workspace; Contract.ThrowIfNull(document); + Contract.ThrowIfNull(workspace); using var progress = BufferedProgress.Create(referenceParams.PartialResultToken); @@ -65,7 +67,7 @@ public FindAllReferencesHandler( ProtocolConversions.PositionToLinePosition(referenceParams.Position), cancellationToken).ConfigureAwait(false); var findUsagesContext = new FindUsagesLSPContext( - progress, document, position, _metadataAsSourceFileService, _asyncListener, _globalOptions, cancellationToken); + progress, workspace, document, position, _metadataAsSourceFileService, _asyncListener, _globalOptions, cancellationToken); // Finds the references for the symbol at the specific position in the document, reporting them via streaming to the LSP client. await findUsagesService.FindReferencesAsync(findUsagesContext, document, position, cancellationToken).ConfigureAwait(false); diff --git a/src/EditorFeatures/Core/LanguageServer/Handlers/References/FindUsagesLSPContext.cs b/src/EditorFeatures/Core/LanguageServer/Handlers/References/FindUsagesLSPContext.cs index 51ebd57f38bc5..671c951054882 100644 --- a/src/EditorFeatures/Core/LanguageServer/Handlers/References/FindUsagesLSPContext.cs +++ b/src/EditorFeatures/Core/LanguageServer/Handlers/References/FindUsagesLSPContext.cs @@ -32,6 +32,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.CustomProtocol internal sealed class FindUsagesLSPContext : FindUsagesContext { private readonly IProgress _progress; + + private readonly Workspace _workspace; private readonly Document _document; private readonly int _position; private readonly IMetadataAsSourceFileService _metadataAsSourceFileService; @@ -74,6 +76,7 @@ internal sealed class FindUsagesLSPContext : FindUsagesContext public FindUsagesLSPContext( IProgress progress, + Workspace workspace, Document document, int position, IMetadataAsSourceFileService metadataAsSourceFileService, @@ -82,6 +85,7 @@ public FindUsagesLSPContext( CancellationToken cancellationToken) { _progress = progress; + _workspace = workspace; _document = document; _position = position; _metadataAsSourceFileService = metadataAsSourceFileService; @@ -247,7 +251,7 @@ public override async ValueTask OnReferenceFoundAsync(SourceReferenceItem refere var options = _globalOptions.GetMetadataAsSourceOptions(_document.Project.Services); var declarationFile = await _metadataAsSourceFileService.GetGeneratedFileAsync( - _document.Project, symbol, signaturesOnly: true, options, cancellationToken).ConfigureAwait(false); + _workspace, _document.Project, symbol, signaturesOnly: true, options, cancellationToken).ConfigureAwait(false); var linePosSpan = declarationFile.IdentifierLocation.GetLineSpan().Span; diff --git a/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.TestContext.cs b/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.TestContext.cs index edeac000cb352..d9e58bb0088ae 100644 --- a/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.TestContext.cs +++ b/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.TestContext.cs @@ -77,7 +77,7 @@ public Task GenerateSourceAsync(ISymbol symbol, Project? p Contract.ThrowIfNull(symbol); // Generate and hold onto the result so it can be disposed of with this context - return _metadataAsSourceService.GetGeneratedFileAsync(project, symbol, signaturesOnly, MetadataAsSourceOptions.GetDefault(project.Services)); + return _metadataAsSourceService.GetGeneratedFileAsync(Workspace, project, symbol, signaturesOnly, MetadataAsSourceOptions.GetDefault(project.Services)); } public async Task GenerateSourceAsync( @@ -135,7 +135,7 @@ public async Task GenerateSourceAsync( } // Generate and hold onto the result so it can be disposed of with this context - var result = await _metadataAsSourceService.GetGeneratedFileAsync(project, symbol, signaturesOnly, options); + var result = await _metadataAsSourceService.GetGeneratedFileAsync(Workspace, project, symbol, signaturesOnly, options); return result; } diff --git a/src/EditorFeatures/Test2/CodeDefinitionWindow/AbstractCodeDefinitionWindowTests.vb b/src/EditorFeatures/Test2/CodeDefinitionWindow/AbstractCodeDefinitionWindowTests.vb index cfe39af1b9f6d..db02323059bab 100644 --- a/src/EditorFeatures/Test2/CodeDefinitionWindow/AbstractCodeDefinitionWindowTests.vb +++ b/src/EditorFeatures/Test2/CodeDefinitionWindow/AbstractCodeDefinitionWindowTests.vb @@ -55,6 +55,7 @@ Namespace Microsoft.CodeAnalysis.Editor.CodeDefinitionWindow.UnitTests Dim definitionContextTracker = workspace.ExportProvider.GetExportedValue(Of DefinitionContextTracker) Dim locations = Await definitionContextTracker.GetContextFromPointAsync( + workspace, document, hostDocument.CursorPosition.Value, CancellationToken.None) @@ -80,6 +81,7 @@ Namespace Microsoft.CodeAnalysis.Editor.CodeDefinitionWindow.UnitTests Dim definitionContextTracker = workspace.ExportProvider.GetExportedValue(Of DefinitionContextTracker) Dim locations = Await definitionContextTracker.GetContextFromPointAsync( + workspace, triggerDocument, triggerHostDocument.CursorPosition.Value, CancellationToken.None) diff --git a/src/EditorFeatures/Test2/CodeDefinitionWindow/CrossLanguageCodeDefinitionWindowTests.vb b/src/EditorFeatures/Test2/CodeDefinitionWindow/CrossLanguageCodeDefinitionWindowTests.vb index 2e15ef4d2172b..76a7034b737d6 100644 --- a/src/EditorFeatures/Test2/CodeDefinitionWindow/CrossLanguageCodeDefinitionWindowTests.vb +++ b/src/EditorFeatures/Test2/CodeDefinitionWindow/CrossLanguageCodeDefinitionWindowTests.vb @@ -111,6 +111,7 @@ Namespace Microsoft.CodeAnalysis.Editor.CodeDefinitionWindow.UnitTests Dim definitionContextTracker = workspace.ExportProvider.GetExportedValue(Of DefinitionContextTracker) Dim locations = Await definitionContextTracker.GetContextFromPointAsync( + workspace, document, hostDocument.CursorPosition.Value, CancellationToken.None) diff --git a/src/Features/Core/Portable/MetadataAsSource/DecompilationMetadataAsSourceFileProvider.cs b/src/Features/Core/Portable/MetadataAsSource/DecompilationMetadataAsSourceFileProvider.cs index 4f73c403fafa3..9a04b4b293bda 100644 --- a/src/Features/Core/Portable/MetadataAsSource/DecompilationMetadataAsSourceFileProvider.cs +++ b/src/Features/Core/Portable/MetadataAsSource/DecompilationMetadataAsSourceFileProvider.cs @@ -40,13 +40,21 @@ public DecompilationMetadataAsSourceFileProvider(IImplementationAssemblyLookupSe _implementationAssemblyLookupService = implementationAssemblyLookupService; } - public async Task GetGeneratedFileAsync(Workspace workspace, Project project, ISymbol symbol, bool signaturesOnly, MetadataAsSourceOptions options, string tempPath, CancellationToken cancellationToken) + public async Task GetGeneratedFileAsync( + MetadataAsSourceWorkspace metadataWorkspace, + Workspace sourceWorkspace, + Project sourceProject, + ISymbol symbol, + bool signaturesOnly, + MetadataAsSourceOptions options, + string tempPath, + CancellationToken cancellationToken) { MetadataAsSourceGeneratedFileInfo fileInfo; Location? navigateLocation = null; var topLevelNamedType = MetadataAsSourceHelpers.GetTopLevelContainingNamedType(symbol); var symbolId = SymbolKey.Create(symbol, cancellationToken); - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var compilation = await sourceProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); // If we've been asked for signatures only, then we never want to use the decompiler var useDecompiler = !signaturesOnly && options.NavigateToDecompiledSources; @@ -68,8 +76,9 @@ public DecompilationMetadataAsSourceFileProvider(IImplementationAssemblyLookupSe useDecompiler = !refInfo.isReferenceAssembly; } - var infoKey = await GetUniqueDocumentKeyAsync(project, topLevelNamedType, signaturesOnly: !useDecompiler, cancellationToken).ConfigureAwait(false); - fileInfo = _keyToInformation.GetOrAdd(infoKey, _ => new MetadataAsSourceGeneratedFileInfo(tempPath, project, topLevelNamedType, signaturesOnly: !useDecompiler)); + var infoKey = await GetUniqueDocumentKeyAsync(sourceProject, topLevelNamedType, signaturesOnly: !useDecompiler, cancellationToken).ConfigureAwait(false); + fileInfo = _keyToInformation.GetOrAdd(infoKey, + _ => new MetadataAsSourceGeneratedFileInfo(tempPath, sourceWorkspace, sourceProject, topLevelNamedType, signaturesOnly: !useDecompiler)); _generatedFilenameToInformation[fileInfo.TemporaryFilePath] = fileInfo; @@ -77,9 +86,10 @@ public DecompilationMetadataAsSourceFileProvider(IImplementationAssemblyLookupSe { // We need to generate this. First, we'll need a temporary project to do the generation into. We // avoid loading the actual file from disk since it doesn't exist yet. - var temporaryProjectInfoAndDocumentId = fileInfo.GetProjectInfoAndDocumentId(workspace, loadFileFromDisk: false); - var temporaryDocument = workspace.CurrentSolution.AddProject(temporaryProjectInfoAndDocumentId.Item1) - .GetRequiredDocument(temporaryProjectInfoAndDocumentId.Item2); + var temporaryProjectInfoAndDocumentId = fileInfo.GetProjectInfoAndDocumentId(metadataWorkspace, loadFileFromDisk: false); + var temporaryDocument = metadataWorkspace.CurrentSolution + .AddProject(temporaryProjectInfoAndDocumentId.Item1) + .GetRequiredDocument(temporaryProjectInfoAndDocumentId.Item2); if (useDecompiler) { @@ -154,7 +164,7 @@ public DecompilationMetadataAsSourceFileProvider(IImplementationAssemblyLookupSe // If we don't have a location yet, then that means we're re-using an existing file. In this case, we'll want to relocate the symbol. if (navigateLocation == null) { - navigateLocation = await RelocateSymbol_NoLockAsync(workspace, fileInfo, symbolId, cancellationToken).ConfigureAwait(false); + navigateLocation = await RelocateSymbol_NoLockAsync(metadataWorkspace, fileInfo, symbolId, cancellationToken).ConfigureAwait(false); } var documentName = string.Format( diff --git a/src/Features/Core/Portable/MetadataAsSource/IMetadataAsSourceFileProvider.cs b/src/Features/Core/Portable/MetadataAsSource/IMetadataAsSourceFileProvider.cs index 17ff476e398f8..aa43bb37c8d89 100644 --- a/src/Features/Core/Portable/MetadataAsSource/IMetadataAsSourceFileProvider.cs +++ b/src/Features/Core/Portable/MetadataAsSource/IMetadataAsSourceFileProvider.cs @@ -15,11 +15,12 @@ internal interface IMetadataAsSourceFileProvider /// /// Generates a file from metadata. Will be called under a lock to prevent concurrent access. /// - Task GetGeneratedFileAsync(Workspace workspace, Project project, ISymbol symbol, bool signaturesOnly, MetadataAsSourceOptions options, string tempPath, CancellationToken cancellationToken); + Task GetGeneratedFileAsync( + MetadataAsSourceWorkspace metadataWorkspace, Workspace sourceWorkspace, Project sourceProject, ISymbol symbol, bool signaturesOnly, MetadataAsSourceOptions options, string tempPath, CancellationToken cancellationToken); /// - /// Called when the file returned from - /// needs to be added to the workspace, to be opened. Will be called under a lock to prevent concurrent access. + /// Called when the file returned from needs to be added to the workspace, + /// to be opened. Will be called under a lock to prevent concurrent access. /// bool TryAddDocumentToWorkspace(Workspace workspace, string filePath, SourceTextContainer sourceTextContainer); diff --git a/src/Features/Core/Portable/MetadataAsSource/IMetadataAsSourceFileService.cs b/src/Features/Core/Portable/MetadataAsSource/IMetadataAsSourceFileService.cs index f1998f54dd643..c62e52be76091 100644 --- a/src/Features/Core/Portable/MetadataAsSource/IMetadataAsSourceFileService.cs +++ b/src/Features/Core/Portable/MetadataAsSource/IMetadataAsSourceFileService.cs @@ -11,19 +11,18 @@ namespace Microsoft.CodeAnalysis.MetadataAsSource internal interface IMetadataAsSourceFileService { /// - /// Generates a file on disk containing general information about the symbol's containing - /// assembly, and the formatted source code for the public, protected, and - /// protected-or-internal interface of which the given ISymbol is or is a part of. + /// Generates a file on disk containing general information about the symbol's containing assembly, and the + /// formatted source code for the public, protected, and protected-or-internal interface of which the given + /// ISymbol is or is a part of. /// - /// The project from which the symbol to generate source for came - /// from. + /// The workspace that came from. + /// The project from which the symbol to generate source for came from. /// The symbol whose interface to generate source for /// to allow a decompiler or other technology to show a /// representation of the original sources; otherwise to only show member /// signatures. /// Options to use when navigating. See for details. - /// To cancel project and document operations - Task GetGeneratedFileAsync(Project project, ISymbol symbol, bool signaturesOnly, MetadataAsSourceOptions options, CancellationToken cancellationToken = default); + Task GetGeneratedFileAsync(Workspace sourceWorkspace, Project sourceProject, ISymbol symbol, bool signaturesOnly, MetadataAsSourceOptions options, CancellationToken cancellationToken = default); bool TryAddDocumentToWorkspace(string filePath, SourceTextContainer buffer); diff --git a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs index 46a1f0bbd464a..747cbc3514926 100644 --- a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs +++ b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceFileService.cs @@ -64,28 +64,29 @@ private string GetRootPathWithGuid_NoLock() return _rootTemporaryPathWithGuid; } - public async Task GetGeneratedFileAsync(Project project, ISymbol symbol, bool signaturesOnly, MetadataAsSourceOptions options, CancellationToken cancellationToken = default) + public async Task GetGeneratedFileAsync( + Workspace sourceWorkspace, + Project sourceProject, + ISymbol symbol, + bool signaturesOnly, + MetadataAsSourceOptions options, + CancellationToken cancellationToken = default) { - if (project == null) - { - throw new ArgumentNullException(nameof(project)); - } + if (sourceProject == null) + throw new ArgumentNullException(nameof(sourceProject)); if (symbol == null) - { throw new ArgumentNullException(nameof(symbol)); - } if (symbol.Kind == SymbolKind.Namespace) - { throw new ArgumentException(FeaturesResources.symbol_cannot_be_a_namespace, nameof(symbol)); - } symbol = symbol.GetOriginalUnreducedDefinition(); using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - InitializeWorkspace(project); + _workspace ??= new MetadataAsSourceWorkspace(this, sourceWorkspace.Services.HostServices); + Contract.ThrowIfNull(_workspace); var tempPath = GetRootPathWithGuid_NoLock(); @@ -93,7 +94,7 @@ public async Task GetGeneratedFileAsync(Project project, I { var provider = lazyProvider.Value; var providerTempPath = Path.Combine(tempPath, provider.GetType().Name); - var result = await provider.GetGeneratedFileAsync(_workspace, project, symbol, signaturesOnly, options, providerTempPath, cancellationToken).ConfigureAwait(false); + var result = await provider.GetGeneratedFileAsync(_workspace, sourceWorkspace, sourceProject, symbol, signaturesOnly, options, providerTempPath, cancellationToken).ConfigureAwait(false); if (result is not null) { return result; @@ -165,11 +166,6 @@ public bool ShouldCollapseOnOpen(string? filePath, BlockStructureOptions blockSt return false; } - private void InitializeWorkspace(Project project) - { - _workspace ??= new MetadataAsSourceWorkspace(this, project.Solution.Workspace.Services.HostServices); - } - internal async Task MapSymbolAsync(Document document, SymbolKey symbolId, CancellationToken cancellationToken) { Contract.ThrowIfNull(document.FilePath); diff --git a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs index a182a2ba44989..5729d27c551f0 100644 --- a/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs +++ b/src/Features/Core/Portable/MetadataAsSource/MetadataAsSourceGeneratedFileInfo.cs @@ -25,20 +25,16 @@ internal sealed class MetadataAsSourceGeneratedFileInfo private readonly ParseOptions? _parseOptions; - public MetadataAsSourceGeneratedFileInfo(string rootPath, Project sourceProject, INamedTypeSymbol topLevelNamedType, bool signaturesOnly) + public MetadataAsSourceGeneratedFileInfo(string rootPath, Workspace sourceWorkspace, Project sourceProject, INamedTypeSymbol topLevelNamedType, bool signaturesOnly) { this.SourceProjectId = sourceProject.Id; - this.Workspace = sourceProject.Solution.Workspace; + this.Workspace = sourceWorkspace; this.LanguageName = signaturesOnly ? sourceProject.Language : LanguageNames.CSharp; this.SignaturesOnly = signaturesOnly; - if (sourceProject.Language == LanguageName) - { - _parseOptions = sourceProject.ParseOptions; - } - else - { - _parseOptions = Workspace.Services.GetLanguageServices(LanguageName).GetRequiredService().GetDefaultParseOptionsWithLatestLanguageVersion(); - } + + _parseOptions = sourceProject.Language == LanguageName + ? sourceProject.ParseOptions + : sourceProject.Solution.Services.GetLanguageServices(LanguageName).GetRequiredService().GetDefaultParseOptionsWithLatestLanguageVersion(); this.References = sourceProject.MetadataReferences.ToImmutableArray(); this.AssemblyIdentity = topLevelNamedType.ContainingAssembly.Identity; diff --git a/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs b/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs index 889c39da2f215..ce26e8be52705 100644 --- a/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs +++ b/src/Features/Core/Portable/PdbSourceDocument/PdbSourceDocumentMetadataAsSourceFileProvider.cs @@ -55,7 +55,15 @@ public PdbSourceDocumentMetadataAsSourceFileProvider( _logger = logger; } - public async Task GetGeneratedFileAsync(Workspace workspace, Project project, ISymbol symbol, bool signaturesOnly, MetadataAsSourceOptions options, string tempPath, CancellationToken cancellationToken) + public async Task GetGeneratedFileAsync( + MetadataAsSourceWorkspace metadataWorkspace, + Workspace sourceWorkspace, + Project sourceProject, + ISymbol symbol, + bool signaturesOnly, + MetadataAsSourceOptions options, + string tempPath, + CancellationToken cancellationToken) { // Check if the user wants to look for PDB source documents at all if (!options.NavigateToSourceLinkAndEmbeddedSources) @@ -74,7 +82,7 @@ public PdbSourceDocumentMetadataAsSourceFileProvider( _logger?.Clear(); _logger?.Log(FeaturesResources.Navigating_to_symbol_0_from_1, symbol, assemblyName); - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var compilation = await sourceProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); // The purpose of the logging is to help library authors, so we don't log things like this where something // else has gone wrong, so even though if this check fails we won't be able to show the source, it's not something @@ -111,7 +119,7 @@ public PdbSourceDocumentMetadataAsSourceFileProvider( // Now that we have the right DLL, we need to look up the symbol in this DLL, because the one // we have is from the reference assembly. To do this we create an empty compilation, // add our DLL as a reference, and use SymbolKey to map the type across. - var compilationFactory = project.Services.GetRequiredService(); + var compilationFactory = sourceProject.Services.GetRequiredService(); var dllReference = IOUtilities.PerformIO(() => MetadataReference.CreateFromFile(dllPath)); if (dllReference is null) { @@ -176,14 +184,14 @@ public PdbSourceDocumentMetadataAsSourceFileProvider( if (!_assemblyToProjectMap.TryGetValue(assemblyName, out var projectId)) { // Get the project info now, so we can dispose the documentDebugInfoReader sooner - var projectInfo = CreateProjectInfo(workspace, project, pdbCompilationOptions, assemblyName, assemblyVersion); + var projectInfo = CreateProjectInfo(metadataWorkspace, sourceProject, pdbCompilationOptions, assemblyName, assemblyVersion); if (projectInfo is null) return null; projectId = projectInfo.Id; - workspace.OnProjectAdded(projectInfo); + metadataWorkspace.OnProjectAdded(projectInfo); _assemblyToProjectMap.Add(assemblyName, projectId); } @@ -210,13 +218,13 @@ public PdbSourceDocumentMetadataAsSourceFileProvider( return null; var symbolId = SymbolKey.Create(symbol, cancellationToken); - var navigateProject = workspace.CurrentSolution.GetRequiredProject(projectId); + var navigateProject = metadataWorkspace.CurrentSolution.GetRequiredProject(projectId); - var documentInfos = CreateDocumentInfos(sourceFileInfos, encoding, navigateProject.Id, project); + var documentInfos = CreateDocumentInfos(sourceFileInfos, encoding, navigateProject.Id, sourceWorkspace, sourceProject); if (documentInfos.Length > 0) { - workspace.OnDocumentsAdded(documentInfos); - navigateProject = workspace.CurrentSolution.GetRequiredProject(projectId); + metadataWorkspace.OnDocumentsAdded(documentInfos); + navigateProject = metadataWorkspace.CurrentSolution.GetRequiredProject(projectId); } // TODO: Support results from multiple source files: https://github.com/dotnet/roslyn/issues/55834 @@ -264,7 +272,8 @@ public PdbSourceDocumentMetadataAsSourceFileProvider( metadataReferences: project.MetadataReferences.ToImmutableArray()); // TODO: Read references from PDB info: https://github.com/dotnet/roslyn/issues/55834 } - private ImmutableArray CreateDocumentInfos(SourceFileInfo?[] sourceFileInfos, Encoding encoding, ProjectId projectId, Project sourceProject) + private ImmutableArray CreateDocumentInfos( + SourceFileInfo?[] sourceFileInfos, Encoding encoding, ProjectId projectId, Workspace sourceWorkspace, Project sourceProject) { using var _ = ArrayBuilder.GetInstance(out var documents); @@ -297,7 +306,7 @@ private ImmutableArray CreateDocumentInfos(SourceFileInfo?[] sourc } // In order to open documents in VS we need to understand the link from temp file to document and its encoding etc. - _fileToDocumentInfoMap[info.FilePath] = new(documentId, encoding, sourceProject.Id, sourceProject.Solution.Workspace); + _fileToDocumentInfoMap[info.FilePath] = new(documentId, encoding, sourceProject.Id, sourceWorkspace); } return documents.ToImmutable(); diff --git a/src/Features/LanguageServer/Protocol/Features/TodoComments/TodoCommentOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/TodoComments/TodoCommentOptionsStorage.cs index 98d500f302525..e0baf1c2e0081 100644 --- a/src/Features/LanguageServer/Protocol/Features/TodoComments/TodoCommentOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/TodoComments/TodoCommentOptionsStorage.cs @@ -16,10 +16,17 @@ internal static class TodoCommentOptionsStorage TodoCommentOptions.Default.TokenList, new RoamingProfileStorageLocation("Microsoft.VisualStudio.ErrorListPkg.Shims.TaskListOptions.CommentTokens")); + public static readonly Option2 ComputeTodoCommentsForClosedFiles = new( + "TodoCommentOptions", + "ComputeTodoCommentsForClosedFiles", + defaultValue: true, + storageLocation: new RoamingProfileStorageLocation($"TextEditor.Specific.ComputeTodoCommentsForClosedFiles")); + public static TodoCommentOptions GetTodoCommentOptions(this IGlobalOptionService globalOptions) => new() { - TokenList = globalOptions.GetOption(TokenList) + TokenList = globalOptions.GetOption(TokenList), + ComputeForClosedFiles = globalOptions.GetOption(ComputeTodoCommentsForClosedFiles) }; } } diff --git a/src/Features/LanguageServer/Protocol/Features/TodoComments/TodoCommentsListener.cs b/src/Features/LanguageServer/Protocol/Features/TodoComments/TodoCommentsListener.cs index c74297d4ef5fc..62d2bc97639d2 100644 --- a/src/Features/LanguageServer/Protocol/Features/TodoComments/TodoCommentsListener.cs +++ b/src/Features/LanguageServer/Protocol/Features/TodoComments/TodoCommentsListener.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; @@ -74,6 +75,11 @@ public async ValueTask StartAsync() // Should only be started once. Contract.ThrowIfTrue(_lazyConnection != null); + // If we're in pull-diagnostics mode, then todo-comments will be handled by LSP. + var diagnosticMode = _globalOptions.GetDiagnosticMode(InternalDiagnosticsOptions.NormalDiagnosticMode); + if (diagnosticMode == DiagnosticMode.Pull) + return; + var cancellationToken = _disposalToken; var client = await RemoteHostClient.TryGetClientAsync(_services, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs index b039215ed03ea..05e86a424d7d1 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs @@ -38,8 +38,9 @@ public AbstractGoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSour protected async Task GetDefinitionAsync(LSP.TextDocumentPositionParams request, bool typeOnly, RequestContext context, CancellationToken cancellationToken) { + var workspace = context.Workspace; var document = context.Document; - if (document == null) + if (workspace is null || document is null) return null; var locations = ArrayBuilder.GetInstance(); @@ -71,7 +72,7 @@ public AbstractGoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSour if (!typeOnly || symbol is ITypeSymbol) { var options = _globalOptions.GetMetadataAsSourceOptions(document.Project.Services); - var declarationFile = await _metadataAsSourceFileService.GetGeneratedFileAsync(document.Project, symbol, signaturesOnly: false, options, cancellationToken).ConfigureAwait(false); + var declarationFile = await _metadataAsSourceFileService.GetGeneratedFileAsync(workspace, document.Project, symbol, signaturesOnly: false, options, cancellationToken).ConfigureAwait(false); var linePosSpan = declarationFile.IdentifierLocation.GetLineSpan().Span; locations.Add(new LSP.Location diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index 67400497e1b2a..3a20f7a70ad7f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -18,13 +18,14 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { + /// /// Root type for both document and workspace diagnostic pull requests. /// /// The LSP input param type /// The LSP type that is reported via IProgress /// The LSP type that is returned on completion of the request. - internal abstract class AbstractPullDiagnosticHandler : IRequestHandler where TDiagnosticsParams : IPartialResultParams + internal abstract partial class AbstractPullDiagnosticHandler : IRequestHandler where TDiagnosticsParams : IPartialResultParams { /// /// Diagnostic mode setting for Razor. This should always be as there is no push support in Razor. @@ -418,6 +419,9 @@ protected static DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData, bool result.Add(VSDiagnosticTags.VisibleInErrorList); } + if (diagnosticData.CustomTags.Contains(PullDiagnosticConstants.TaskItemCustomTag)) + result.Add(VSDiagnosticTags.TaskItem); + if (potentialDuplicate) result.Add(VSDiagnosticTags.PotentialDuplicate); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs new file mode 100644 index 0000000000000..49189fd47d5cb --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs @@ -0,0 +1,107 @@ +// 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.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.TodoComments; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; + +internal abstract class AbstractDocumentDiagnosticSource : IDiagnosticSource + where TDocument : TextDocument +{ + private static readonly ImmutableArray s_todoCommentCustomTags = ImmutableArray.Create(PullDiagnosticConstants.TaskItemCustomTag); + + private static Tuple, ImmutableArray> s_lastRequestedTokens = + Tuple.Create(ImmutableArray.Empty, ImmutableArray.Empty); + + protected readonly TDocument Document; + + protected AbstractDocumentDiagnosticSource(TDocument document) + { + this.Document = document; + } + + public ProjectOrDocumentId GetId() => new(Document.Id); + public Project GetProject() => Document.Project; + public Uri GetUri() => Document.GetURI(); + + protected abstract bool IncludeTodoComments { get; } + protected abstract bool IncludeStandardDiagnostics { get; } + + protected abstract Task> GetDiagnosticsWorkerAsync( + IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, DiagnosticMode diagnosticMode, CancellationToken cancellationToken); + + public async Task> GetDiagnosticsAsync( + IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, DiagnosticMode diagnosticMode, CancellationToken cancellationToken) + { + var todoComments = IncludeTodoComments ? await this.GetTodoCommentDiagnosticsAsync(cancellationToken).ConfigureAwait(false) : ImmutableArray.Empty; + var diagnostics = IncludeStandardDiagnostics ? await this.GetDiagnosticsWorkerAsync(diagnosticAnalyzerService, context, diagnosticMode, cancellationToken).ConfigureAwait(false) : ImmutableArray.Empty; + return todoComments.AddRange(diagnostics); + } + + private async Task> GetTodoCommentDiagnosticsAsync(CancellationToken cancellationToken) + { + if (this.Document is not Document document) + return ImmutableArray.Empty; + + var service = document.GetLanguageService(); + if (service == null) + return ImmutableArray.Empty; + + var tokenList = document.Project.Solution.Options.GetOption(TodoCommentOptionsStorage.TokenList); + var descriptors = GetAndCacheDescriptors(tokenList); + + var comments = await service.GetTodoCommentsAsync(document, descriptors, cancellationToken).ConfigureAwait(false); + if (comments.Length == 0) + return ImmutableArray.Empty; + + using var _ = ArrayBuilder.GetInstance(out var converted); + await TodoComment.ConvertAsync(document, comments, converted, cancellationToken).ConfigureAwait(false); + + return converted.SelectAsArray(comment => new DiagnosticData( + id: "TODO", + category: "TODO", + message: comment.Message, + severity: DiagnosticSeverity.Info, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + warningLevel: 0, + customTags: s_todoCommentCustomTags, + properties: ImmutableDictionary.Empty, + projectId: document.Project.Id, + language: document.Project.Language, + location: new DiagnosticDataLocation( + document.Id, + originalFilePath: comment.OriginalFilePath, + mappedFilePath: comment.MappedFilePath, + originalStartLine: comment.OriginalLine, + originalStartColumn: comment.OriginalColumn, + originalEndLine: comment.OriginalLine, + originalEndColumn: comment.OriginalColumn, + mappedStartLine: comment.MappedLine, + mappedStartColumn: comment.MappedColumn, + mappedEndLine: comment.MappedLine, + mappedEndColumn: comment.MappedColumn))); + } + + private static ImmutableArray GetAndCacheDescriptors(ImmutableArray tokenList) + { + var lastRequested = s_lastRequestedTokens; + if (!lastRequested.Item1.SequenceEqual(tokenList)) + { + var descriptors = TodoCommentDescriptor.Parse(tokenList); + lastRequested = Tuple.Create(tokenList, descriptors); + s_lastRequestedTokens = lastRequested; + } + + return lastRequested.Item2; + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 145ac97b3d70e..90863825d9f9d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -2,7 +2,6 @@ // 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.Immutable; using System.Threading; using System.Threading.Tasks; @@ -10,13 +9,19 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal sealed record class DocumentDiagnosticSource(Document Document) : IDiagnosticSource +internal sealed class DocumentDiagnosticSource : AbstractDocumentDiagnosticSource { - public ProjectOrDocumentId GetId() => new(Document.Id); - public Project GetProject() => Document.Project; - public Uri GetUri() => Document.GetURI(); + public DocumentDiagnosticSource(Document document) : base(document) + { + } + + // The normal diagnostic source includes both todo comments and diagnostics for this open file. + + protected override bool IncludeTodoComments => true; + protected override bool IncludeStandardDiagnostics => true; - public async Task> GetDiagnosticsAsync(IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, DiagnosticMode diagnosticMode, CancellationToken cancellationToken) + protected override async Task> GetDiagnosticsWorkerAsync( + IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, DiagnosticMode diagnosticMode, CancellationToken cancellationToken) { // We call GetDiagnosticsForSpanAsync here instead of GetDiagnosticsForIdsAsync as it has faster perf characteristics. // GetDiagnosticsForIdsAsync runs analyzers against the entire compilation whereas GetDiagnosticsForSpanAsync will only run analyzers against the request document. diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/WorkspaceDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/WorkspaceDocumentDiagnosticSource.cs index 5f13cd16b8ad3..4c7259d1cee87 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/WorkspaceDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/WorkspaceDocumentDiagnosticSource.cs @@ -7,16 +7,27 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal sealed record class WorkspaceDocumentDiagnosticSource(TextDocument Document) : IDiagnosticSource +internal sealed class WorkspaceDocumentDiagnosticSource : AbstractDocumentDiagnosticSource { - public ProjectOrDocumentId GetId() => new(Document.Id); - public Project GetProject() => Document.Project; - public Uri GetUri() => Document.GetURI(); + protected override bool IncludeTodoComments { get; } + protected override bool IncludeStandardDiagnostics { get; } - public async Task> GetDiagnosticsAsync( + public WorkspaceDocumentDiagnosticSource( + TextDocument document, + bool includeTodoComments, + bool includeStandardDiagnostics) : base(document) + { + Contract.ThrowIfFalse(includeTodoComments || includeStandardDiagnostics, + $"At least one of includeTodoComments={includeTodoComments} or includeStandardDiagnostics={includeStandardDiagnostics} must be true."); + IncludeTodoComments = includeTodoComments; + IncludeStandardDiagnostics = includeStandardDiagnostics; + } + + protected override async Task> GetDiagnosticsWorkerAsync( IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, DiagnosticMode diagnosticMode, diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index f6bcab6cc9c14..ea7657391be27 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -2,20 +2,14 @@ // 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.Immutable; -using System.Composition; -using System.Reflection.Metadata; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.EditAndContinue; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Roslyn.Utilities; -using Range = Microsoft.VisualStudio.LanguageServer.Protocol.Range; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs index 98dbff29b0bbd..f17cdacd1f9cc 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs @@ -69,9 +69,7 @@ protected override DocumentDiagnosticPartialReport CreateUnchangedReport(TextDoc } protected override ValueTask> GetOrderedDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) - { - return ValueTaskFactory.FromResult(DocumentPullDiagnosticHandler.GetDiagnosticSources(context)); - } + => ValueTaskFactory.FromResult(DocumentPullDiagnosticHandler.GetDiagnosticSources(context)); protected override ImmutableArray? GetPreviousResults(DocumentDiagnosticParams diagnosticsParams) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs index 4953e26ad3ca8..3806b68f12a2f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs @@ -4,14 +4,12 @@ using System; using System.Collections.Immutable; -using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.EditAndContinue; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -38,19 +36,19 @@ protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData) } protected override WorkspaceDiagnosticReport CreateReport(TextDocumentIdentifier identifier, VisualStudio.LanguageServer.Protocol.Diagnostic[] diagnostics, string resultId) - => new WorkspaceDiagnosticReport(new[] + => new(new[] { new WorkspaceDocumentDiagnosticReport(new WorkspaceFullDocumentDiagnosticReport(identifier.Uri, diagnostics, version: null, resultId)) }); protected override WorkspaceDiagnosticReport CreateRemovedReport(TextDocumentIdentifier identifier) - => new WorkspaceDiagnosticReport(new[] + => new(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(new[] { new WorkspaceDocumentDiagnosticReport(new WorkspaceUnchangedDocumentDiagnosticReport(identifier.Uri, resultId, version: null)) }); @@ -65,9 +63,7 @@ protected override WorkspaceDiagnosticReport CreateUnchangedReport(TextDocumentI } protected override ValueTask> GetOrderedDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) - { - return WorkspacePullDiagnosticHandler.GetDiagnosticSourcesAsync(context, GlobalOptions, cancellationToken); - } + => WorkspacePullDiagnosticHandler.GetDiagnosticSourcesAsync(context, GlobalOptions, cancellationToken); protected override ImmutableArray? GetPreviousResults(WorkspaceDiagnosticParams diagnosticsParams) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticConstants.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticConstants.cs new file mode 100644 index 0000000000000..2a74e800e2e55 --- /dev/null +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/PullDiagnosticConstants.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 Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics +{ + internal static class PullDiagnosticConstants + { + public const string TaskItemCustomTag = nameof(TaskItemCustomTag); + } +} diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index 9336d42f13ef1..59936f3e11f83 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -3,27 +3,20 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Composition; using System.Linq; -using System.Reflection.Metadata; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.ExternalAccess.Razor.Api; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SolutionCrawler; -using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.TodoComments; using Microsoft.VisualStudio.LanguageServer.Protocol; using Roslyn.Utilities; -using Range = Microsoft.VisualStudio.LanguageServer.Protocol.Range; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics { @@ -66,9 +59,7 @@ protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData) } protected override ValueTask> GetOrderedDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken) - { - return GetDiagnosticSourcesAsync(context, GlobalOptions, cancellationToken); - } + => GetDiagnosticSourcesAsync(context, GlobalOptions, cancellationToken); protected override VSInternalWorkspaceDiagnosticReport[]? CreateReturn(BufferedProgress progress) { @@ -123,13 +114,14 @@ async Task AddDocumentsAndProject(Project? project, ImmutableArray suppo return; } - // Only process closed files if FSA is on. - if (!globalOptions.IsFullSolutionAnalysisEnabled(project.Language)) + var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language); + var todoCommentsEnabled = globalOptions.GetTodoCommentOptions().ComputeForClosedFiles; + if (!fullSolutionAnalysisEnabled && !todoCommentsEnabled) return; var documents = ImmutableArray.Empty.AddRange(project.Documents).AddRange(project.AdditionalDocuments); - // If all features are enabled for source generated documents, make sure they are included when computing diagnostics. + // If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them. if (solution.Services.GetService()?.Options.EnableOpeningSourceGeneratedFiles == true) { var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); @@ -151,11 +143,12 @@ async Task AddDocumentsAndProject(Project? project, ImmutableArray suppo if (document.IsRazorDocument()) continue; - result.Add(new WorkspaceDocumentDiagnosticSource(document)); + result.Add(new WorkspaceDocumentDiagnosticSource(document, includeTodoComments: true, includeStandardDiagnostics: fullSolutionAnalysisEnabled)); } - // Finally, we also want to check for diagnostics associated with the project itself. - result.Add(new ProjectDiagnosticSource(project)); + // Finally if fsa is on, we also want to check for diagnostics associated with the project itself. + if (fullSolutionAnalysisEnabled) + result.Add(new ProjectDiagnosticSource(project)); } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs b/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs index cab18fc1665bc..b443b64d4c8a5 100644 --- a/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/ProjectContext/GetTextDocumentWithContextHandler.cs @@ -32,6 +32,7 @@ public GetTextDocumentWithContextHandler() public Task HandleRequestAsync(VSGetProjectContextsParams request, RequestContext context, CancellationToken cancellationToken) { + Contract.ThrowIfNull(context.Workspace); Contract.ThrowIfNull(context.Solution); // We specifically don't use context.Document here because we want multiple @@ -71,7 +72,7 @@ public GetTextDocumentWithContextHandler() // ID in GetDocumentIdsWithFilePath, but there's really nothing we can do since we don't have contexts for // close documents anyways. var openDocument = documents.First(); - var currentContextDocumentId = openDocument.Project.Solution.Workspace.GetDocumentIdInCurrentContext(openDocument.Id); + var currentContextDocumentId = context.Workspace.GetDocumentIdInCurrentContext(openDocument.Id); return Task.FromResult(new VSProjectContextList { diff --git a/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs b/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs index 2c1dae5e9ff62..d2e5eee6cfc9f 100644 --- a/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs +++ b/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs @@ -37,10 +37,24 @@ internal readonly struct RequestContext private readonly LspServices _lspServices; /// - /// The solution state that the request should operate on, if the handler requires an LSP solution, or otherwise + /// The workspace this request is for, if applicable. This will be present if is + /// present. It will be if requiresLSPSolution is false. + /// + public readonly Workspace? Workspace; + + /// + /// The solution state that the request should operate on, if the handler requires an LSP solution, or otherwise /// public readonly Solution? Solution; + /// + /// The document that the request is for, if applicable. This comes from the returned from the handler itself via a call to . + /// + public readonly Document? Document; + /// /// The client capabilities for the request. /// @@ -51,11 +65,6 @@ internal readonly struct RequestContext /// public readonly WellKnownLspServerKinds ServerKind; - /// - /// The document that the request is for, if applicable. This comes from the returned from the handler itself via a call to . - /// - public readonly Document? Document; - /// /// The languages supported by the server making the request. /// @@ -69,19 +78,21 @@ internal readonly struct RequestContext private readonly ILspLogger _logger; public RequestContext( + Workspace? workspace, Solution? solution, + Document? document, ILspLogger logger, ClientCapabilities clientCapabilities, WellKnownLspServerKinds serverKind, - Document? document, IDocumentChangeTracker documentChangeTracker, ImmutableDictionary trackedDocuments, ImmutableArray supportedLanguages, LspServices lspServices, CancellationToken queueCancellationToken) { - Document = document; + Workspace = workspace; Solution = solution; + Document = document; ClientCapabilities = clientCapabilities; ServerKind = serverKind; SupportedLanguages = supportedLanguages; @@ -118,35 +129,38 @@ public RequestContext( if (!requiresLSPSolution) { return new RequestContext( - solution: null, logger: logger, clientCapabilities: clientCapabilities, serverKind: serverKind, document: null, - documentChangeTracker: documentChangeTracker, trackedDocuments: trackedDocuments, supportedLanguages: supportedLanguages, lspServices: lspServices, - queueCancellationToken: queueCancellationToken); + workspace: null, solution: null, document: null, logger, clientCapabilities, serverKind, + documentChangeTracker, trackedDocuments, supportedLanguages, lspServices, queueCancellationToken); } - Solution? workspaceSolution; + Workspace? workspace = null; + Solution? solution = null; Document? document = null; if (textDocument is not null) { - // we were given a request associated with a document. Find the corresponding roslyn document for this. - // There are certain cases where we may be asked for a document that does not exist (for example a document is removed) - // For example, document pull diagnostics can ask us after removal to clear diagnostics for a document. - (_, document) = await lspWorkspaceManager.GetLspDocumentAsync(textDocument, requestCancellationToken).ConfigureAwait(false); + // we were given a request associated with a document. Find the corresponding roslyn document for this. + // There are certain cases where we may be asked for a document that does not exist (for example a + // document is removed) For example, document pull diagnostics can ask us after removal to clear + // diagnostics for a document. + (workspace, solution, document) = await lspWorkspaceManager.GetLspDocumentInfoAsync(textDocument, requestCancellationToken).ConfigureAwait(false); } - workspaceSolution = document?.Project.Solution ?? await lspWorkspaceManager.TryGetHostLspSolutionAsync(requestCancellationToken).ConfigureAwait(false); + if (workspace is null) + (workspace, solution) = await lspWorkspaceManager.GetLspSolutionInfoAsync(requestCancellationToken).ConfigureAwait(false); - if (workspaceSolution == null) + if (workspace is null) { - logger.TraceError("Could not find appropriate solution for operation"); + logger.TraceError("Could not find appropriate workspace for operation"); return null; } var context = new RequestContext( - workspaceSolution, + workspace, + solution, + document, logger, clientCapabilities, serverKind, - document, documentChangeTracker, trackedDocuments, supportedLanguages, diff --git a/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs b/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs index 1652dec371ebb..f0a327ffee2d5 100644 --- a/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs +++ b/src/Features/LanguageServer/Protocol/Workspaces/LspWorkspaceManager.cs @@ -147,28 +147,30 @@ public void UpdateTrackedDocument(Uri uri, SourceText newSourceText) #region LSP Solution Retrieval /// - /// Returns the LSP solution associated with the workspace with the specified . - /// This is the solution used for LSP requests that pertain to the entire workspace, for example code search or workspace diagnostics. + /// Returns the LSP solution associated with the workspace with the specified . This + /// is the solution used for LSP requests that pertain to the entire workspace, for example code search or workspace + /// diagnostics. /// /// This is always called serially in the when creating the . /// - public async Task TryGetHostLspSolutionAsync(CancellationToken cancellationToken) + public async Task<(Workspace?, Solution?)> GetLspSolutionInfoAsync(CancellationToken cancellationToken) { // Ensure we have the latest lsp solutions var updatedSolutions = await GetLspSolutionsAsync(cancellationToken).ConfigureAwait(false); - var (_, hostWorkspaceSolution, isForked) = updatedSolutions.FirstOrDefault(lspSolution => lspSolution.Solution.WorkspaceKind == _hostWorkspaceKind); + var (hostWorkspace, hostWorkspaceSolution, isForked) = updatedSolutions.FirstOrDefault(lspSolution => lspSolution.Solution.WorkspaceKind == _hostWorkspaceKind); _requestTelemetryLogger.UpdateUsedForkedSolutionCounter(isForked); - return hostWorkspaceSolution; + return (hostWorkspace, hostWorkspaceSolution); } /// /// Returns a document with the LSP tracked text forked from the appropriate workspace solution. /// - /// This is always called serially in the when creating the . + /// This is always called serially in the when creating the . /// - public async Task<(Workspace?, Document?)> GetLspDocumentAsync(TextDocumentIdentifier textDocumentIdentifier, CancellationToken cancellationToken) + public async Task<(Workspace?, Solution?, Document?)> GetLspDocumentInfoAsync(TextDocumentIdentifier textDocumentIdentifier, CancellationToken cancellationToken) { var uri = textDocumentIdentifier.Uri; @@ -189,7 +191,7 @@ public void UpdateTrackedDocument(Uri uri, SourceText newSourceText) _requestTelemetryLogger.UpdateUsedForkedSolutionCounter(isForked); _logger.TraceInformation($"{document.FilePath} found in workspace {workspaceKind}"); - return (workspace, document); + return (workspace, document.Project.Solution, document); } } @@ -202,7 +204,8 @@ public void UpdateTrackedDocument(Uri uri, SourceText newSourceText) if (_trackedDocuments.ContainsKey(uri)) { var miscDocument = _lspMiscellaneousFilesWorkspace?.AddMiscellaneousDocument(uri, _trackedDocuments[uri], _logger); - return (_lspMiscellaneousFilesWorkspace, miscDocument); + if (miscDocument is not null) + return (_lspMiscellaneousFilesWorkspace, miscDocument.Project.Solution, miscDocument); } return default; diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs index 0fc48e2e314c1..bad2a7b5d4068 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs @@ -19,6 +19,7 @@ using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.TodoComments; using Microsoft.VisualStudio.LanguageServer.Protocol; using Roslyn.Test.Utilities; using Roslyn.Utilities; @@ -52,17 +53,20 @@ private protected static async Task> RunGet TestLspServer testLspServer, bool useVSDiagnostics, ImmutableArray<(string resultId, Uri uri)>? previousResults = null, - bool useProgress = false) + bool useProgress = false, + bool includeTodoComments = false) { + var optionService = testLspServer.TestWorkspace.ExportProvider.GetExportedValue(); + optionService.SetGlobalOption(new OptionKey(TodoCommentOptionsStorage.ComputeTodoCommentsForClosedFiles), includeTodoComments); await testLspServer.WaitForDiagnosticsAsync(); if (useVSDiagnostics) { BufferedProgress? progress = useProgress ? BufferedProgress.Create(null) : null; var diagnostics = await testLspServer.ExecuteRequestAsync( - VSInternalMethods.WorkspacePullDiagnosticName, - CreateWorkspaceDiagnosticParams(previousResults, progress), - CancellationToken.None).ConfigureAwait(false); + VSInternalMethods.WorkspacePullDiagnosticName, + CreateWorkspaceDiagnosticParams(previousResults, progress), + CancellationToken.None).ConfigureAwait(false); if (useProgress) { diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs index edb1f109d1541..f61542e0d8b7c 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs @@ -70,6 +70,30 @@ public async Task TestDocumentDiagnosticsForOpenFilesWithFSAOff(bool useVSDiagno Assert.NotNull(results.Single().Diagnostics.Single().CodeDescription!.Href); } + [Theory, CombinatorialData] + public async Task TestDocumentTodoCommentsDiagnosticsForOpenFile(bool useVSDiagnostics) + { + var markup = +@" +// todo: goo +class A { +}"; + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + + // Calling GetTextBuffer will effectively open the file. + testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); + + var document = testLspServer.GetCurrentSolution().Projects.Single().Documents.Single(); + + await OpenDocumentAsync(testLspServer, document); + + var results = await RunGetDocumentPullDiagnosticsAsync( + testLspServer, document.GetURI(), useVSDiagnostics); + + Assert.Equal("TODO", results.Single().Diagnostics.Single().Code); + Assert.Equal("todo: goo", results.Single().Diagnostics.Single().Message); + } + [Theory, CombinatorialData] public async Task TestNoDocumentDiagnosticsForOpenFilesWithFSAOffIfInPushMode(bool useVSDiagnostics) { @@ -701,6 +725,102 @@ public async Task TestWorkspaceDiagnosticsForClosedFilesWithFSAOn(bool useVSDiag Assert.Empty(results[2].Diagnostics); } + [Theory, CombinatorialData] + public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOff(bool useVSDiagnostics) + { + var markup1 = +@" +// todo: goo +class A { +}"; + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + new[] { markup1 }, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTodoComments: false); + + Assert.Equal(0, results.Length); + } + + [Theory, CombinatorialData] + public async Task TestWorkspaceTodoForClosedFilesWithFSAOffAndTodoOn(bool useVSDiagnostics) + { + var markup1 = +@" +// todo: goo +class A { +}"; + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + new[] { markup1 }, BackgroundAnalysisScope.OpenFiles, useVSDiagnostics); + + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTodoComments: true); + + Assert.Equal(1, results.Length); + Assert.Equal("TODO", results[0].Diagnostics.Single().Code); + Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); + } + + [Theory, CombinatorialData] + public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOff(bool useVSDiagnostics) + { + var markup1 = +@" +// todo: goo +class A { +}"; + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + new[] { markup1 }, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTodoComments: false); + + Assert.Equal(2, results.Length); + + Assert.Equal("TODO", results[0].Diagnostics.Single().Code); + Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); + Assert.Empty(results[1].Diagnostics); + } + + [Theory, CombinatorialData] + public async Task TestWorkspaceTodoForClosedFilesWithFSAOnAndTodoOn(bool useVSDiagnostics) + { + var markup1 = +@" +// todo: goo +class A { +}"; + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + new[] { markup1 }, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTodoComments: true); + + Assert.Equal(2, results.Length); + + Assert.Equal("TODO", results[0].Diagnostics.Single().Code); + Assert.Equal("todo: goo", results[0].Diagnostics.Single().Message); + Assert.Empty(results[1].Diagnostics); + } + + [Theory, CombinatorialData] + public async Task TestWorkspaceTodoAndDiagnosticForClosedFilesWithFSAOnAndTodoOn(bool useVSDiagnostics) + { + var markup1 = +@" +// todo: goo +class A { +"; + using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + new[] { markup1 }, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, includeTodoComments: true); + + Assert.Equal(2, results.Length); + + Assert.Equal("TODO", results[0].Diagnostics![0].Code); + Assert.Equal("todo: goo", results[0].Diagnostics![0].Message); + Assert.Equal("CS1513", results[0].Diagnostics![1].Code); + + Assert.Empty(results[1].Diagnostics); + } + [Theory, CombinatorialData] public async Task TestNoWorkspaceDiagnosticsForClosedFilesWithFSAOffWithFileInProjectOpen(bool useVSDiagnostics) { diff --git a/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.LinkedDocuments.cs b/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.LinkedDocuments.cs index 94f298a675212..491ced7fb8b9e 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.LinkedDocuments.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.LinkedDocuments.cs @@ -101,7 +101,7 @@ void M() private static async Task GetLSPSolutionAsync(TestLspServer testLspServer, Uri uri) { - var (_, lspDocument) = await testLspServer.GetManager().GetLspDocumentAsync(new TextDocumentIdentifier { Uri = uri }, CancellationToken.None).ConfigureAwait(false); + var (_, _, lspDocument) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new TextDocumentIdentifier { Uri = uri }, CancellationToken.None).ConfigureAwait(false); Contract.ThrowIfNull(lspDocument); return lspDocument.Project.Solution; } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs index cca19aa4ca827..36daed66d0ee4 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs @@ -175,19 +175,19 @@ void M() await testLspServer.OpenDocumentAsync(looseFileUri, source).ConfigureAwait(false); // Verify the request on the loose file fails. - var (_, lspDocument) = await testLspServer.GetManager().GetLspDocumentAsync(new LSP.TextDocumentIdentifier { Uri = looseFileUri }, CancellationToken.None).ConfigureAwait(false); + var (_, _, lspDocument) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { Uri = looseFileUri }, CancellationToken.None).ConfigureAwait(false); Assert.Null(lspDocument); } private static async Task AssertFileInMiscWorkspaceAsync(TestLspServer testLspServer, Uri fileUri) { - var (lspWorkspace, _) = await testLspServer.GetManager().GetLspDocumentAsync(new LSP.TextDocumentIdentifier { Uri = fileUri }, CancellationToken.None); + var (lspWorkspace, _, _) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { Uri = fileUri }, CancellationToken.None); Assert.Equal(testLspServer.GetManagerAccessor().GetLspMiscellaneousFilesWorkspace(), lspWorkspace); } private static async Task AssertFileInMainWorkspaceAsync(TestLspServer testLspServer, Uri fileUri) { - var (lspWorkspace, _) = await testLspServer.GetManager().GetLspDocumentAsync(new LSP.TextDocumentIdentifier { Uri = fileUri }, CancellationToken.None).ConfigureAwait(false); + var (lspWorkspace, _, _) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { Uri = fileUri }, CancellationToken.None).ConfigureAwait(false); Assert.Equal(testLspServer.TestWorkspace, lspWorkspace); } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs index 4bf617fc97983..d6a02dbaa9006 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Ordering/RequestOrderingTests.cs @@ -256,7 +256,7 @@ private static async Task ExecuteDidOpen(TestLspServer testLspServer, Uri docume Contract.ThrowIfNull(response); if (response.ContextHasSolution) { - var solution = await testLspServer.GetManager().TryGetHostLspSolutionAsync(CancellationToken.None).ConfigureAwait(false); + var (_, solution) = await testLspServer.GetManager().GetLspSolutionInfoAsync(CancellationToken.None).ConfigureAwait(false); Contract.ThrowIfNull(solution); return solution; } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/SpellCheck/SpellCheckTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/SpellCheck/SpellCheckTests.cs index 5601ec7df2dd9..c1af4cd7f94cb 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/SpellCheck/SpellCheckTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/SpellCheck/SpellCheckTests.cs @@ -168,7 +168,7 @@ public async Task TestDocumentResultChangedAfterEntityAdded() await InsertTextAsync(testLspServer, document, buffer.CurrentSnapshot.Length, "// comment"); - var lspSolution = await testLspServer.GetManager().TryGetHostLspSolutionAsync(CancellationToken.None).ConfigureAwait(false); + var (_, lspSolution) = await testLspServer.GetManager().GetLspSolutionInfoAsync(CancellationToken.None).ConfigureAwait(false); document = lspSolution!.Projects.Single().Documents.Single(); results = await RunGetDocumentSpellCheckSpansAsync(testLspServer, document.GetURI(), results.Single().ResultId); @@ -464,7 +464,7 @@ public async Task TestWorkspaceResultUpdatedAfterEdit() var results2 = await RunGetWorkspaceSpellCheckSpansAsync(testLspServer, previousResults: CreateParamsFromPreviousReports(results)); Assert.Equal(2, results2.Length); - var lspSolution = await testLspServer.GetManager().TryGetHostLspSolutionAsync(CancellationToken.None).ConfigureAwait(false); + var (_, lspSolution) = await testLspServer.GetManager().GetLspSolutionInfoAsync(CancellationToken.None).ConfigureAwait(false); document = lspSolution!.Projects.Single().Documents.First(); sourceText = await document.GetTextAsync(); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs index b5abd7a9654c8..708d936447962 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs @@ -29,20 +29,20 @@ public async Task TestUsesLspTextOnOpenCloseAsync() await testLspServer.OpenDocumentAsync(documentUri, "LSP text"); - var (_, lspDocument) = await GetLspDocumentAsync(documentUri, testLspServer).ConfigureAwait(false); + var (_, lspDocument) = await GetLspWorkspaceAndDocumentAsync(documentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(lspDocument); Assert.Equal("LSP text", (await lspDocument.GetTextAsync(CancellationToken.None)).ToString()); // Verify LSP text changes are reflected in the opened document. await testLspServer.InsertTextAsync(documentUri, (0, 0, "More text")); - (_, lspDocument) = await GetLspDocumentAsync(documentUri, testLspServer).ConfigureAwait(false); + (_, lspDocument) = await GetLspWorkspaceAndDocumentAsync(documentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(lspDocument); Assert.Equal("More textLSP text", (await lspDocument.GetTextAsync(CancellationToken.None)).ToString()); // Close the document in LSP and verify all LSP tracked changes are now gone. // The document should be reset to the workspace's state. await testLspServer.CloseDocumentAsync(documentUri); - var (_, closedDocument) = await GetLspDocumentAsync(documentUri, testLspServer).ConfigureAwait(false); + var (_, closedDocument) = await GetLspWorkspaceAndDocumentAsync(documentUri, testLspServer).ConfigureAwait(false); Assert.Equal(testLspServer.GetCurrentSolution(), closedDocument!.Project.Solution); } @@ -68,8 +68,8 @@ public async Task TestLspUsesWorkspaceInstanceOnChangesAsync() await testLspServer.InsertTextAsync(firstDocumentUri, (0, 0, "Some more text")); await testLspServer.TestWorkspace.ChangeDocumentAsync(firstDocument.Id, SourceText.From($"Some more text{markupOne}", System.Text.Encoding.UTF8)); - var (_, firstDocumentWithChange) = await GetLspDocumentAsync(firstDocumentUri, testLspServer).ConfigureAwait(false); - var (_, secondDocumentUnchanged) = await GetLspDocumentAsync(secondDocumentUri, testLspServer).ConfigureAwait(false); + var (_, firstDocumentWithChange) = await GetLspWorkspaceAndDocumentAsync(firstDocumentUri, testLspServer).ConfigureAwait(false); + var (_, secondDocumentUnchanged) = await GetLspWorkspaceAndDocumentAsync(secondDocumentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(firstDocumentWithChange); AssertEx.NotNull(secondDocumentUnchanged); @@ -103,12 +103,12 @@ public async Task TestLspHasClosedDocumentChangesAsync() await testLspServer.TestWorkspace.ChangeDocumentAsync(secondDocument.Id, SourceText.From("Two is now three!", System.Text.Encoding.UTF8)); // Verify that the LSP solution has the LSP text from the open document. - var (_, openedDocument) = await GetLspDocumentAsync(firstDocumentUri, testLspServer).ConfigureAwait(false); + var (_, openedDocument) = await GetLspWorkspaceAndDocumentAsync(firstDocumentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(openedDocument); Assert.Equal("LSP text", (await openedDocument.GetTextAsync(CancellationToken.None)).ToString()); // Verify that the LSP solution has the workspace text in the closed document. - (_, secondDocument) = await GetLspDocumentAsync(secondDocumentUri, testLspServer).ConfigureAwait(false); + (_, secondDocument) = await GetLspWorkspaceAndDocumentAsync(secondDocumentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(secondDocument); Assert.Equal("Two is now three!", (await secondDocument.GetTextAsync()).ToString()); Assert.NotEqual(testLspServer.TestWorkspace.CurrentSolution.GetDocument(secondDocument.Id), secondDocument); @@ -131,7 +131,7 @@ public async Task TestLspHasProjectChangesAsync() await testLspServer.TestWorkspace.ChangeProjectAsync(newProject.Id, newProject.Solution); // Verify that the new LSP solution has the updated project info. - (_, openedDocument) = await GetLspDocumentAsync(documentUri, testLspServer).ConfigureAwait(false); + (_, openedDocument) = await GetLspWorkspaceAndDocumentAsync(documentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(openedDocument); Assert.Equal(markup, (await openedDocument.GetTextAsync(CancellationToken.None)).ToString()); Assert.Equal("NewCSProj1", openedDocument.Project.AssemblyName); @@ -155,7 +155,7 @@ public async Task TestLspHasProjectChangesWithForkedTextAsync() await testLspServer.TestWorkspace.ChangeProjectAsync(newProject.Id, newProject.Solution); // Verify that the new LSP solution has the updated project info. - (_, openedDocument) = await GetLspDocumentAsync(documentUri, testLspServer).ConfigureAwait(false); + (_, openedDocument) = await GetLspWorkspaceAndDocumentAsync(documentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(openedDocument); Assert.Equal("LSP text", (await openedDocument.GetTextAsync(CancellationToken.None)).ToString()); Assert.Equal("NewCSProj1", openedDocument.Project.AssemblyName); @@ -180,7 +180,7 @@ public async Task TestLspFindsNewDocumentAsync() // Verify that the lsp server sees the workspace change and picks up the document in the correct workspace. await testLspServer.OpenDocumentAsync(newDocumentUri); - var (_, lspDocument) = await GetLspDocumentAsync(newDocumentUri, testLspServer).ConfigureAwait(false); + var (_, lspDocument) = await GetLspWorkspaceAndDocumentAsync(newDocumentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(lspDocument); Assert.Equal(testLspServer.TestWorkspace.CurrentSolution, lspDocument.Project.Solution); } @@ -203,14 +203,14 @@ public async Task TestLspTransfersDocumentToNewWorkspaceAsync() await testLspServer.OpenDocumentAsync(newDocumentUri, "LSP text"); // Verify it is in the lsp misc workspace. - var (miscWorkspace, miscDocument) = await GetLspDocumentAsync(newDocumentUri, testLspServer).ConfigureAwait(false); + var (miscWorkspace, miscDocument) = await GetLspWorkspaceAndDocumentAsync(newDocumentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(miscDocument); Assert.Equal(testLspServer.GetManagerAccessor().GetLspMiscellaneousFilesWorkspace(), miscWorkspace); Assert.Equal("LSP text", (await miscDocument.GetTextAsync(CancellationToken.None)).ToString()); // Make a change and verify the misc document is updated. await testLspServer.InsertTextAsync(newDocumentUri, (0, 0, "More LSP text")); - (_, miscDocument) = await GetLspDocumentAsync(newDocumentUri, testLspServer).ConfigureAwait(false); + (_, miscDocument) = await GetLspWorkspaceAndDocumentAsync(newDocumentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(miscDocument); var miscText = await miscDocument.GetTextAsync(CancellationToken.None); Assert.Equal("More LSP textLSP text", miscText.ToString()); @@ -219,7 +219,7 @@ public async Task TestLspTransfersDocumentToNewWorkspaceAsync() await testLspServer.TestWorkspace.AddDocumentAsync(newDocumentInfo); // Verify that the newly added document in the registered workspace is returned. - var (documentWorkspace, document) = await GetLspDocumentAsync(newDocumentUri, testLspServer).ConfigureAwait(false); + var (documentWorkspace, document) = await GetLspWorkspaceAndDocumentAsync(newDocumentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(document); Assert.Equal(testLspServer.TestWorkspace, documentWorkspace); Assert.Equal(newDocumentId, document.Id); @@ -268,7 +268,7 @@ public async Task TestUsesRegisteredHostWorkspace() Assert.True(IsWorkspaceRegistered(testWorkspaceTwo, testLspServer)); // Verify the host workspace returned is the workspace with kind host. - var hostSolution = await GetLspHostSolutionAsync(testLspServer).ConfigureAwait(false); + var (_, hostSolution) = await GetLspHostWorkspaceAndSolutionAsync(testLspServer).ConfigureAwait(false); AssertEx.NotNull(hostSolution); Assert.Equal("FirstWorkspaceProject", hostSolution.Projects.First().Name); } @@ -293,7 +293,8 @@ public async Task TestWorkspaceRequestFailsWhenHostWorkspaceMissing() Assert.True(IsWorkspaceRegistered(testLspServer.TestWorkspace, testLspServer)); // Verify there is not workspace matching the host workspace kind. - Assert.Null(await GetLspHostSolutionAsync(testLspServer).ConfigureAwait(false)); + var (_, solution) = await GetLspHostWorkspaceAndSolutionAsync(testLspServer).ConfigureAwait(false); + Assert.Null(solution); } [Fact] @@ -333,12 +334,12 @@ public async Task TestLspUpdatesCorrectWorkspaceWithMultipleWorkspacesAsync() await testLspServer.OpenDocumentAsync(firstWorkspaceDocumentUri); // Verify we can get both documents from their respective workspaces. - var (firstWorkspace, firstDocument) = await GetLspDocumentAsync(firstWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); + var (firstWorkspace, firstDocument) = await GetLspWorkspaceAndDocumentAsync(firstWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(firstDocument); Assert.Equal(firstWorkspaceDocumentUri, firstDocument.GetURI()); Assert.Equal(testLspServer.TestWorkspace, firstWorkspace); - var (secondWorkspace, secondDocument) = await GetLspDocumentAsync(secondWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); + var (secondWorkspace, secondDocument) = await GetLspWorkspaceAndDocumentAsync(secondWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(secondDocument); Assert.Equal(secondWorkspaceDocumentUri, secondDocument.GetURI()); Assert.Equal(testWorkspaceTwo, secondWorkspace); @@ -347,14 +348,14 @@ public async Task TestLspUpdatesCorrectWorkspaceWithMultipleWorkspacesAsync() await testLspServer.InsertTextAsync(firstWorkspaceDocumentUri, (0, 0, "Change in first workspace")); // The first document should now different text. - var (_, changedFirstDocument) = await GetLspDocumentAsync(firstWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); + var (_, changedFirstDocument) = await GetLspWorkspaceAndDocumentAsync(firstWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(changedFirstDocument); var changedFirstDocumentText = await changedFirstDocument.GetTextAsync(CancellationToken.None); var firstDocumentText = await firstDocument.GetTextAsync(CancellationToken.None); Assert.NotEqual(firstDocumentText, changedFirstDocumentText); // The second document should return the same document instance since it was not changed. - var (_, unchangedSecondDocument) = await GetLspDocumentAsync(secondWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); + var (_, unchangedSecondDocument) = await GetLspWorkspaceAndDocumentAsync(secondWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); Assert.Equal(secondDocument, unchangedSecondDocument); } @@ -392,12 +393,12 @@ public async Task TestWorkspaceEventUpdatesCorrectWorkspaceWithMultipleWorkspace await testLspServer.OpenDocumentAsync(firstWorkspaceDocumentUri); // Verify we can get both documents from their respective workspaces. - var (firstWorkspace, firstDocument) = await GetLspDocumentAsync(firstWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); + var (firstWorkspace, firstDocument) = await GetLspWorkspaceAndDocumentAsync(firstWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(firstDocument); Assert.Equal(firstWorkspaceDocumentUri, firstDocument.GetURI()); Assert.Equal(testLspServer.TestWorkspace, firstWorkspace); - var (secondWorkspace, secondDocument) = await GetLspDocumentAsync(secondWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); + var (secondWorkspace, secondDocument) = await GetLspWorkspaceAndDocumentAsync(secondWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(secondDocument); Assert.Equal(secondWorkspaceDocumentUri, secondDocument.GetURI()); Assert.Equal(testWorkspaceTwo, secondWorkspace); @@ -407,13 +408,13 @@ public async Task TestWorkspaceEventUpdatesCorrectWorkspaceWithMultipleWorkspace await testWorkspaceTwo.ChangeProjectAsync(newProjectWorkspaceTwo.Id, newProjectWorkspaceTwo.Solution); // The second document should have an updated project assembly name. - var (_, secondDocumentChangedProject) = await GetLspDocumentAsync(secondWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); + var (_, secondDocumentChangedProject) = await GetLspWorkspaceAndDocumentAsync(secondWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(secondDocumentChangedProject); Assert.Equal("NewCSProj1", secondDocumentChangedProject.Project.AssemblyName); Assert.NotEqual(secondDocument, secondDocumentChangedProject); // The first document should be the same document as the last one since that workspace was not changed. - var (_, lspDocument) = await GetLspDocumentAsync(firstWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); + var (_, lspDocument) = await GetLspWorkspaceAndDocumentAsync(firstWorkspaceDocumentUri, testLspServer).ConfigureAwait(false); Assert.Equal(firstDocument, lspDocument); } @@ -446,7 +447,7 @@ public async Task TestSeparateWorkspaceManagerPerServerAsync() // Verify that the LSP solution uses the correct text for each server. var documentServerOne = await OpenDocumentAndVerifyLspTextAsync(documentUri, testLspServerOne, "Server one text"); - var (_, documentServerTwo) = await GetLspDocumentAsync(documentUri, testLspServerTwo).ConfigureAwait(false); + var (_, documentServerTwo) = await GetLspWorkspaceAndDocumentAsync(documentUri, testLspServerTwo).ConfigureAwait(false); AssertEx.NotNull(documentServerTwo); Assert.Equal("Original text", (await documentServerTwo.GetTextAsync(CancellationToken.None)).ToString()); @@ -456,10 +457,10 @@ public async Task TestSeparateWorkspaceManagerPerServerAsync() await testWorkspace.ChangeProjectAsync(newProject.Id, newProject.Solution); // Verify LSP solution has the project changes. - (_, documentServerOne) = await GetLspDocumentAsync(documentUri, testLspServerOne).ConfigureAwait(false); + (_, documentServerOne) = await GetLspWorkspaceAndDocumentAsync(documentUri, testLspServerOne).ConfigureAwait(false); AssertEx.NotNull(documentServerOne); Assert.Equal(newAssemblyName, documentServerOne.Project.AssemblyName); - (_, documentServerTwo) = await GetLspDocumentAsync(documentUri, testLspServerTwo).ConfigureAwait(false); + (_, documentServerTwo) = await GetLspWorkspaceAndDocumentAsync(documentUri, testLspServerTwo).ConfigureAwait(false); AssertEx.NotNull(documentServerTwo); Assert.Equal(newAssemblyName, documentServerTwo.Project.AssemblyName); } @@ -469,7 +470,7 @@ private static async Task OpenDocumentAndVerifyLspTextAsync(Uri docume await testLspServer.OpenDocumentAsync(documentUri, openText); // Verify we can find the document with correct text in the new LSP solution. - var (_, lspDocument) = await GetLspDocumentAsync(documentUri, testLspServer).ConfigureAwait(false); + var (_, lspDocument) = await GetLspWorkspaceAndDocumentAsync(documentUri, testLspServer).ConfigureAwait(false); AssertEx.NotNull(lspDocument); Assert.Equal(openText, (await lspDocument.GetTextAsync(CancellationToken.None)).ToString()); return lspDocument; @@ -480,13 +481,14 @@ private static bool IsWorkspaceRegistered(Workspace workspace, TestLspServer tes return testLspServer.GetManagerAccessor().IsWorkspaceRegistered(workspace); } - private static Task<(Workspace? workspace, Document? document)> GetLspDocumentAsync(Uri uri, TestLspServer testLspServer) + private static async Task<(Workspace? workspace, Document? document)> GetLspWorkspaceAndDocumentAsync(Uri uri, TestLspServer testLspServer) { - return testLspServer.GetManager().GetLspDocumentAsync(CreateTextDocumentIdentifier(uri), CancellationToken.None); + var (workspace, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(CreateTextDocumentIdentifier(uri), CancellationToken.None).ConfigureAwait(false); + return (workspace, document); } - private static Task GetLspHostSolutionAsync(TestLspServer testLspServer) + private static Task<(Workspace?, Solution?)> GetLspHostWorkspaceAndSolutionAsync(TestLspServer testLspServer) { - return testLspServer.GetManager().TryGetHostLspSolutionAsync(CancellationToken.None); + return testLspServer.GetManager().GetLspSolutionInfoAsync(CancellationToken.None); } } diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs index 47915a7578c3d..ab39f5093680a 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioSymbolNavigationService.cs @@ -36,6 +36,7 @@ internal partial class VisualStudioSymbolNavigationService : ForegroundThreadAff private readonly IGlobalOptionService _globalOptions; private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactory; private readonly IMetadataAsSourceFileService _metadataAsSourceFileService; + private readonly VisualStudioWorkspace _workspace; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -44,13 +45,15 @@ public VisualStudioSymbolNavigationService( IGlobalOptionService globalOptions, IThreadingContext threadingContext, IVsEditorAdaptersFactoryService editorAdaptersFactory, - IMetadataAsSourceFileService metadataAsSourceFileService) + IMetadataAsSourceFileService metadataAsSourceFileService, + VisualStudioWorkspace workspace) : base(threadingContext) { _serviceProvider = serviceProvider; _globalOptions = globalOptions; _editorAdaptersFactory = editorAdaptersFactory; _metadataAsSourceFileService = metadataAsSourceFileService; + _workspace = workspace; } public async Task GetNavigableLocationAsync( @@ -123,7 +126,7 @@ public VisualStudioSymbolNavigationService( { var masOptions = _globalOptions.GetMetadataAsSourceOptions(project.Services); - var result = await _metadataAsSourceFileService.GetGeneratedFileAsync(project, symbol, signaturesOnly: false, masOptions, cancellationToken).ConfigureAwait(false); + var result = await _metadataAsSourceFileService.GetGeneratedFileAsync(_workspace, project, symbol, signaturesOnly: false, masOptions, cancellationToken).ConfigureAwait(false); return new NavigableLocation(async (options, cancellationToken) => { diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs index 315bf3205308b..347792cb85aae 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs @@ -170,11 +170,12 @@ public GoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFileSe { if (metadataAsSourceFileService.IsNavigableMetadataSymbol(symbol)) { + var workspace = context.Workspace; var project = context.Document?.GetCodeProject(); - if (project != null) + if (workspace != null && project != null) { var options = globalOptions.GetMetadataAsSourceOptions(project.Services); - var declarationFile = await metadataAsSourceFileService.GetGeneratedFileAsync(project, symbol, signaturesOnly: true, options, cancellationToken).ConfigureAwait(false); + var declarationFile = await metadataAsSourceFileService.GetGeneratedFileAsync(workspace, project, symbol, signaturesOnly: true, options, cancellationToken).ConfigureAwait(false); var linePosSpan = declarationFile.IdentifierLocation.GetLineSpan().Span; locations.Add(new LSP.Location { diff --git a/src/Workspaces/Core/Portable/TodoComments/TodoCommentOptions.cs b/src/Workspaces/Core/Portable/TodoComments/TodoCommentOptions.cs index c3e6883b376fb..58c1045ca3de8 100644 --- a/src/Workspaces/Core/Portable/TodoComments/TodoCommentOptions.cs +++ b/src/Workspaces/Core/Portable/TodoComments/TodoCommentOptions.cs @@ -16,6 +16,9 @@ internal readonly record struct TodoCommentOptions [DataMember] public ImmutableArray TokenList { get; init; } = s_defaultTokenList; + [DataMember] + public bool ComputeForClosedFiles { get; init; } = true; + public TodoCommentOptions() { }