diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index 32f6c5f3b02e..75e43208dc92 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -87,12 +87,6 @@ Task> GetDiagnosticsForSpanAsync( Task> GetDiagnosticDescriptorsAsync( Solution solution, ProjectId projectId, AnalyzerReference analyzerReference, string language, CancellationToken cancellationToken); - /// - /// Given a list of errors ids (like CS1234), attempts to find an associated descriptor for each id. - /// - Task> TryGetDiagnosticDescriptorsAsync( - Solution solution, ImmutableArray diagnosticIds, CancellationToken cancellationToken); - /// Task>> GetDiagnosticDescriptorsPerReferenceAsync( Solution solution, CancellationToken cancellationToken); diff --git a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_RemoteOrLocalDispatcher.cs b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_RemoteOrLocalDispatcher.cs index 536fac2ea0bf..cc549b40ae84 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_RemoteOrLocalDispatcher.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService_RemoteOrLocalDispatcher.cs @@ -60,35 +60,6 @@ public async Task> GetDiagnosticDescriptors .SelectManyAsArray(this._analyzerInfoCache.GetDiagnosticDescriptors); } - public async Task> TryGetDiagnosticDescriptorsAsync( - Solution solution, ImmutableArray diagnosticIds, CancellationToken cancellationToken) - { - var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false); - if (client is not null) - { - var map = await client.TryInvokeAsync>( - solution, - (service, solution, cancellationToken) => service.TryGetDiagnosticDescriptorsAsync(solution, diagnosticIds, cancellationToken), - cancellationToken).ConfigureAwait(false); - - if (!map.HasValue) - return ImmutableDictionary.Empty; - - return map.Value.ToImmutableDictionary( - kvp => kvp.Key, - kvp => kvp.Value.ToDiagnosticDescriptor()); - } - - var builder = ImmutableDictionary.CreateBuilder(); - foreach (var diagnosticId in diagnosticIds) - { - if (this._analyzerInfoCache.TryGetDescriptorForDiagnosticId(diagnosticId, out var descriptor)) - builder[diagnosticId] = descriptor; - } - - return builder.ToImmutable(); - } - public async Task>> GetDiagnosticDescriptorsPerReferenceAsync(Solution solution, CancellationToken cancellationToken) { var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false); diff --git a/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs b/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs index 3739d37a541e..b5f9eb0dc6ee 100644 --- a/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs +++ b/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs @@ -40,8 +40,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; internal sealed class ExternalErrorDiagnosticUpdateSource : IDisposable { private readonly Workspace _workspace; - public readonly IAsynchronousOperationListener Listener; - public readonly CancellationToken DisposalToken; private readonly IServiceBroker _serviceBroker; /// @@ -70,17 +68,14 @@ public ExternalErrorDiagnosticUpdateSource( [Import(typeof(SVsFullAccessServiceBroker))] IServiceBroker serviceBroker, IThreadingContext threadingContext) { - DisposalToken = threadingContext.DisposalToken; _workspace = workspace; - Listener = listenerProvider.GetListener(FeatureAttribute.ErrorList); _serviceBroker = serviceBroker; _taskQueue = new AsyncBatchingWorkQueue>( TimeSpan.Zero, processBatchAsync: ProcessTaskQueueItemsAsync, - Listener, - DisposalToken - ); + listenerProvider.GetListener(FeatureAttribute.ErrorList), + threadingContext.DisposalToken); // This pattern ensures that we are called whenever the build starts/completes even if it is already in progress. KnownUIContexts.SolutionBuildingContext.WhenActivated(() => diff --git a/src/VisualStudio/Core/Def/TaskList/ProjectExternalErrorReporter.cs b/src/VisualStudio/Core/Def/TaskList/ProjectExternalErrorReporter.cs index c174bb48cb7b..e0519e60ab8a 100644 --- a/src/VisualStudio/Core/Def/TaskList/ProjectExternalErrorReporter.cs +++ b/src/VisualStudio/Core/Def/TaskList/ProjectExternalErrorReporter.cs @@ -5,83 +5,46 @@ using System; using System.Collections.Immutable; using System.Diagnostics; -using System.Globalization; using System.Linq; using System.Runtime.InteropServices; -using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.Threading; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.Implementation.Venus; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TextManager.Interop; -using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; -internal sealed class ProjectExternalErrorReporter : IVsReportExternalErrors, IVsLanguageServiceBuildErrorReporter2 +internal sealed class ProjectExternalErrorReporter( + ProjectId projectId, + Guid projectHierarchyGuid, + string errorCodePrefix, + string language, + VisualStudioWorkspaceImpl workspace) : IVsReportExternalErrors, IVsLanguageServiceBuildErrorReporter2 { internal static readonly ImmutableArray CustomTags = [WellKnownDiagnosticTags.Telemetry]; internal static readonly ImmutableArray CompilerDiagnosticCustomTags = [WellKnownDiagnosticTags.Compiler, WellKnownDiagnosticTags.Telemetry]; - private readonly ProjectId _projectId; + private readonly ProjectId _projectId = projectId; /// /// Passed to the error reporting service to allow current project error list filters to work. /// - private readonly Guid _projectHierarchyGuid; - private readonly string _errorCodePrefix; - private readonly string _language; + private readonly Guid _projectHierarchyGuid = projectHierarchyGuid; + private readonly string _errorCodePrefix = errorCodePrefix; + private readonly string _language = language; - private readonly VisualStudioWorkspaceImpl _workspace; - - /// - /// Protects , serializing all access to it. - /// - private readonly AsyncBatchingWorkQueue> _taskQueue; - - public ProjectExternalErrorReporter( - ProjectId projectId, - Guid projectHierarchyGuid, - string errorCodePrefix, - string language, - VisualStudioWorkspaceImpl workspace) - { - _projectId = projectId; - _projectHierarchyGuid = projectHierarchyGuid; - _errorCodePrefix = errorCodePrefix; - _language = language; - _workspace = workspace; - - _taskQueue = new AsyncBatchingWorkQueue>( - TimeSpan.Zero, - ProcessWorkAsync, - DiagnosticProvider.Listener, - DiagnosticProvider.DisposalToken); - } + private readonly VisualStudioWorkspaceImpl _workspace = workspace; private ExternalErrorDiagnosticUpdateSource DiagnosticProvider => _workspace.ExternalErrorDiagnosticUpdateSource; - private async ValueTask ProcessWorkAsync( - ImmutableSegmentedList> list, CancellationToken cancellationToken) - { - foreach (var func in list) - { - await func(cancellationToken) - .ReportNonFatalErrorUnlessCancelledAsync(cancellationToken) - .ConfigureAwait(false); - } - } - private static ImmutableArray GetExternalErrors(IVsEnumExternalErrors pErrors) { using var _ = ArrayBuilder.GetInstance(out var allErrors); @@ -102,35 +65,24 @@ public int AddNewErrors(IVsEnumExternalErrors pErrors) var project = solution.GetProject(_projectId); if (project != null) { - // First, grab all the data from the input. Do this on the current thread in case that enumerator has - // thread affinity. - var allErrors = GetExternalErrors(pErrors); - - // Then, jump to the background to actually process the errors. - _taskQueue.AddWork(cancellationToken => - AddNewErrorsAsync(project, allErrors, cancellationToken)); + AddNewErrors(project, GetExternalErrors(pErrors)); } return VSConstants.S_OK; } - private async Task AddNewErrorsAsync( - Project project, ImmutableArray allErrors, CancellationToken cancellationToken) + private void AddNewErrors(Project project, ImmutableArray allErrors) { using var _ = ArrayBuilder.GetInstance(out var allDiagnostics); var solution = project.Solution; var errorIds = allErrors.Select(e => e.iErrorID).Distinct().Select(GetErrorId).ToImmutableArray(); - var diagnosticService = solution.Services.GetRequiredService(); - var errorIdToDescriptor = await diagnosticService.TryGetDiagnosticDescriptorsAsync( - solution, errorIds, cancellationToken).ConfigureAwait(false); foreach (var error in allErrors) { if (error.bstrFileName != null) { - var diagnostic = await TryCreateDocumentDiagnosticItemAsync( - project, error, errorIdToDescriptor, cancellationToken).ConfigureAwait(false); + var diagnostic = TryCreateDocumentDiagnosticItem(project, error); if (diagnostic != null) { allDiagnostics.Add(diagnostic); @@ -138,16 +90,14 @@ private async Task AddNewErrorsAsync( } } - allDiagnostics.Add(await GetDiagnosticDataAsync( + allDiagnostics.Add(GetDiagnosticData( project, documentId: null, GetErrorId(error), error.bstrText, GetDiagnosticSeverity(error), _language, - new FileLinePositionSpan(project.FilePath ?? "", span: default), - errorIdToDescriptor, - cancellationToken).ConfigureAwait(false)); + new FileLinePositionSpan(project.FilePath ?? "", span: default))); } DiagnosticProvider.AddNewErrors(_projectId, _projectHierarchyGuid, allDiagnostics.ToImmutableAndClear()); @@ -161,14 +111,7 @@ public int ClearErrors() private int ClearErrorsWorker() { - // Cancel any inflight work. No point in processing the work to *add* errors, just to clear them when this task runs. - _taskQueue.AddWork(cancellationToken => - { - DiagnosticProvider.ClearErrors(_projectId); - return Task.CompletedTask; - }, - cancelExistingWork: true); - + DiagnosticProvider.ClearErrors(_projectId); return VSConstants.S_OK; } @@ -186,11 +129,9 @@ public int GetErrors(out IVsEnumExternalErrors? pErrors) .FirstOrDefault(); } - private async Task TryCreateDocumentDiagnosticItemAsync( + private DiagnosticData? TryCreateDocumentDiagnosticItem( Project project, - ExternalError error, - ImmutableDictionary errorIdToDescriptor, - CancellationToken cancellationToken) + ExternalError error) { var documentId = TryGetDocumentId(error.bstrFileName); if (documentId == null) @@ -230,7 +171,7 @@ public int GetErrors(out IVsEnumExternalErrors? pErrors) // save error line/column (surface buffer location) as mapped line/column so that we can display // right location on closed Venus file. - return await GetDiagnosticDataAsync( + return GetDiagnosticData( project, documentId, GetErrorId(error), @@ -239,9 +180,7 @@ public int GetErrors(out IVsEnumExternalErrors? pErrors) _language, new FileLinePositionSpan(error.bstrFileName, new LinePosition(line, column), - new LinePosition(line, column)), - errorIdToDescriptor, - cancellationToken).ConfigureAwait(false); + new LinePosition(line, column))); } public int ReportError(string bstrErrorMessage, string bstrErrorId, [ComAliasName("VsShell.VSTASKPRIORITY")] VSTASKPRIORITY nPriority, int iLine, int iColumn, string bstrFileName) @@ -271,145 +210,86 @@ public void ReportError2( return; } - _taskQueue.AddWork(ReportErrorAsync); + if (!bstrErrorId.StartsWith(_errorCodePrefix)) + return; - async Task ReportErrorAsync(CancellationToken cancellationToken) + if ((iEndLine >= 0 && iEndColumn >= 0) && + ((iEndLine < iStartLine) || + (iEndLine == iStartLine && iEndColumn < iStartColumn))) { - // we accept all compiler diagnostics - if (!bstrErrorId.StartsWith(_errorCodePrefix) && - !await DiagnosticProvider.IsSupportedDiagnosticIdAsync( - _projectId, bstrErrorId, cancellationToken).ConfigureAwait(false)) - { - return; - } - - if ((iEndLine >= 0 && iEndColumn >= 0) && - ((iEndLine < iStartLine) || - (iEndLine == iStartLine && iEndColumn < iStartColumn))) - { - throw new ArgumentException(ServicesVSResources.End_position_must_be_start_position); - } - - var severity = nPriority switch - { - VSTASKPRIORITY.TP_HIGH => DiagnosticSeverity.Error, - VSTASKPRIORITY.TP_NORMAL => DiagnosticSeverity.Warning, - VSTASKPRIORITY.TP_LOW => DiagnosticSeverity.Info, - _ => throw new ArgumentException(ServicesVSResources.Not_a_valid_value, nameof(nPriority)) - }; + throw new ArgumentException(ServicesVSResources.End_position_must_be_start_position); + } - DocumentId? documentId; - if (iStartLine < 0 || iStartColumn < 0) - { - documentId = null; - iStartLine = iStartColumn = iEndLine = iEndColumn = 0; - } - else - { - documentId = TryGetDocumentId(bstrFileName); - } + var severity = nPriority switch + { + VSTASKPRIORITY.TP_HIGH => DiagnosticSeverity.Error, + VSTASKPRIORITY.TP_NORMAL => DiagnosticSeverity.Warning, + VSTASKPRIORITY.TP_LOW => DiagnosticSeverity.Info, + _ => throw new ArgumentException(ServicesVSResources.Not_a_valid_value, nameof(nPriority)) + }; + + DocumentId? documentId; + if (iStartLine < 0 || iStartColumn < 0) + { + documentId = null; + iStartLine = iStartColumn = iEndLine = iEndColumn = 0; + } + else + { + documentId = TryGetDocumentId(bstrFileName); + } - if (iEndLine < 0) - iEndLine = iStartLine; - if (iEndColumn < 0) - iEndColumn = iStartColumn; + if (iEndLine < 0) + iEndLine = iStartLine; + if (iEndColumn < 0) + iEndColumn = iStartColumn; - var solution = _workspace.CurrentSolution; - var project = solution.GetProject(_projectId); - if (project is null) - return; + var solution = _workspace.CurrentSolution; + var project = solution.GetProject(_projectId); + if (project is null) + return; - var diagnosticService = solution.Services.GetRequiredService(); - var errorIdToDescriptor = await diagnosticService.TryGetDiagnosticDescriptorsAsync( - solution, [bstrErrorId], cancellationToken).ConfigureAwait(false); + var diagnostic = GetDiagnosticData( + project, + documentId, + bstrErrorId, + bstrErrorMessage, + severity, + _language, + new FileLinePositionSpan( + bstrFileName, + new LinePosition(iStartLine, iStartColumn), + new LinePosition(iEndLine, iEndColumn))); - var diagnostic = await GetDiagnosticDataAsync( - project, - documentId, - bstrErrorId, - bstrErrorMessage, - severity, - _language, - new FileLinePositionSpan( - bstrFileName, - new LinePosition(iStartLine, iStartColumn), - new LinePosition(iEndLine, iEndColumn)), - errorIdToDescriptor, - cancellationToken).ConfigureAwait(false); - - DiagnosticProvider.AddNewErrors(_projectId, _projectHierarchyGuid, [diagnostic]); - } + DiagnosticProvider.AddNewErrors(_projectId, _projectHierarchyGuid, [diagnostic]); } - private static async Task GetDiagnosticDataAsync( + private static DiagnosticData GetDiagnosticData( Project project, DocumentId? documentId, string errorId, string message, DiagnosticSeverity severity, string language, - FileLinePositionSpan unmappedSpan, - ImmutableDictionary errorIdToDescriptor, - CancellationToken cancellationToken) + FileLinePositionSpan unmappedSpan) { - string title, description, category; - string? helpLink; - DiagnosticSeverity defaultSeverity; - bool isEnabledByDefault; - ImmutableArray customTags; - - if (errorIdToDescriptor.TryGetValue(errorId, out var descriptor)) - { - title = descriptor.Title.ToString(CultureInfo.CurrentUICulture); - description = descriptor.Description.ToString(CultureInfo.CurrentUICulture); - category = descriptor.Category; - defaultSeverity = descriptor.DefaultSeverity; - isEnabledByDefault = descriptor.IsEnabledByDefault; - customTags = descriptor.CustomTags.AsImmutableOrEmpty(); - helpLink = descriptor.HelpLinkUri; - } - else - { - title = message; - description = message; - category = WellKnownDiagnosticTags.Build; - defaultSeverity = severity; - isEnabledByDefault = true; - customTags = IsCompilerDiagnostic(errorId) ? CompilerDiagnosticCustomTags : CustomTags; - helpLink = null; - } - - var diagnostic = new DiagnosticData( + return new DiagnosticData( id: errorId, - category: category, + category: WellKnownDiagnosticTags.Build, message: message, - title: title, - description: description, + title: message, + description: message, severity: severity, - defaultSeverity: defaultSeverity, - isEnabledByDefault: isEnabledByDefault, - warningLevel: (severity == DiagnosticSeverity.Error) ? 0 : 1, - customTags: customTags, + defaultSeverity: severity, + isEnabledByDefault: true, + warningLevel: severity == DiagnosticSeverity.Error ? 0 : 1, + customTags: IsCompilerDiagnostic(errorId) ? CompilerDiagnosticCustomTags : CustomTags, properties: DiagnosticData.PropertiesForBuildDiagnostic, projectId: project.Id, location: new DiagnosticDataLocation( unmappedSpan, documentId), - language: language, - helpLink: helpLink); - - if (documentId != null && - project.GetDocument(documentId) is Document document && - document.SupportsSyntaxTree) - { - var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var text = await tree.GetTextAsync(cancellationToken).ConfigureAwait(false); - var span = diagnostic.DataLocation.UnmappedFileSpan.GetClampedTextSpan(text); - var location = diagnostic.DataLocation.WithSpan(span, tree); - return diagnostic.WithLocations(location, additionalLocations: default); - } - - return diagnostic; + language: language); } private static bool IsCompilerDiagnostic(string errorId) diff --git a/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs b/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs index 96af244c0002..b085a5382944 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs @@ -55,9 +55,6 @@ ValueTask> ProduceProjectDiagnosticsAsync( ValueTask> GetDiagnosticDescriptorsAsync( Checksum solutionChecksum, ProjectId projectId, string analyzerReferenceFullPath, string language, CancellationToken cancellationToken); - ValueTask> TryGetDiagnosticDescriptorsAsync( - Checksum solutionChecksum, ImmutableArray diagnosticIds, CancellationToken cancellationToken); - ValueTask>> GetDiagnosticDescriptorsPerReferenceAsync( Checksum solutionChecksum, CancellationToken cancellationToken); diff --git a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs index f8e4b2690039..125df37ef758 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs @@ -151,24 +151,6 @@ public ValueTask> GetDiagnosticDescript cancellationToken); } - public ValueTask> TryGetDiagnosticDescriptorsAsync( - Checksum solutionChecksum, - ImmutableArray diagnosticIds, - CancellationToken cancellationToken) - { - return RunWithSolutionAsync( - solutionChecksum, - async solution => - { - var service = solution.Services.GetRequiredService(); - var map = await service.TryGetDiagnosticDescriptorsAsync(solution, diagnosticIds, cancellationToken).ConfigureAwait(false); - return map.ToImmutableDictionary( - kvp => kvp.Key, - kvp => DiagnosticDescriptorData.Create(kvp.Value)); - }, - cancellationToken); - } - public ValueTask>> GetDiagnosticDescriptorsPerReferenceAsync( Checksum solutionChecksum, CancellationToken cancellationToken)