From 8d50823cf00748e146c5100aa7b865dcdefc965c Mon Sep 17 00:00:00 2001 From: David Barbet Date: Wed, 2 Oct 2024 18:24:04 -0700 Subject: [PATCH] Log and report NFW when we fail to apply project system update --- .../HostWorkspace/WorkspaceProject.cs | 285 +++++++++--------- .../WorkspaceProjectFactoryService.cs | 34 ++- .../LanguageServerFatalError.cs | 25 ++ 3 files changed, 196 insertions(+), 148 deletions(-) create mode 100644 src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServerFatalError.cs diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs index b92e94ddf9c7e..e8b9ed22b7088 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProject.cs @@ -2,11 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer.Handler.DebugConfiguration; using Microsoft.CodeAnalysis.Remote.ProjectSystem; using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; +using Microsoft.Extensions.Logging; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; @@ -16,76 +16,78 @@ internal class WorkspaceProject : IWorkspaceProject private readonly ProjectSystemProject _project; private readonly ProjectSystemProjectOptionsProcessor _optionsProcessor; private readonly ProjectTargetFrameworkManager _targetFrameworkManager; + private readonly ILogger _logger; - public WorkspaceProject(ProjectSystemProject project, SolutionServices solutionServices, ProjectTargetFrameworkManager targetFrameworkManager) + public WorkspaceProject(ProjectSystemProject project, SolutionServices solutionServices, ProjectTargetFrameworkManager targetFrameworkManager, ILoggerFactory logger) { _project = project; _optionsProcessor = new ProjectSystemProjectOptionsProcessor(_project, solutionServices); _targetFrameworkManager = targetFrameworkManager; + _logger = logger.CreateLogger(); } [Obsolete($"Call the {nameof(AddAdditionalFilesAsync)} overload that takes {nameof(SourceFileInfo)}.")] - public async Task AddAdditionalFilesAsync(IReadOnlyList additionalFilePaths, CancellationToken cancellationToken) + public Task AddAdditionalFilesAsync(IReadOnlyList additionalFilePaths, CancellationToken cancellationToken) { - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - foreach (var additionalFilePath in additionalFilePaths) - _project.AddAdditionalFile(additionalFilePath); + return RunAndReportNFWAsync(() => + { + foreach (var additionalFilePath in additionalFilePaths) + _project.AddAdditionalFile(additionalFilePath); + }, cancellationToken); } - public async Task AddAdditionalFilesAsync(IReadOnlyList additionalFiles, CancellationToken cancellationToken) + public Task AddAdditionalFilesAsync(IReadOnlyList additionalFiles, CancellationToken cancellationToken) { - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - foreach (var additionalFile in additionalFiles) - _project.AddAdditionalFile(additionalFile.FilePath, folders: [.. additionalFile.FolderNames]); + return RunAndReportNFWAsync(() => + { + foreach (var additionalFile in additionalFiles) + _project.AddAdditionalFile(additionalFile.FilePath, folders: [.. additionalFile.FolderNames]); + }, cancellationToken); } - public async Task AddAnalyzerConfigFilesAsync(IReadOnlyList analyzerConfigPaths, CancellationToken cancellationToken) + public Task AddAnalyzerConfigFilesAsync(IReadOnlyList analyzerConfigPaths, CancellationToken cancellationToken) { - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - foreach (var analyzerConfigPath in analyzerConfigPaths) - _project.AddAnalyzerConfigFile(analyzerConfigPath); + return RunAndReportNFWAsync(() => + { + foreach (var analyzerConfigPath in analyzerConfigPaths) + _project.AddAnalyzerConfigFile(analyzerConfigPath); + }, cancellationToken); } - public async Task AddAnalyzerReferencesAsync(IReadOnlyList analyzerPaths, CancellationToken cancellationToken) + public Task AddAnalyzerReferencesAsync(IReadOnlyList analyzerPaths, CancellationToken cancellationToken) { - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - foreach (var analyzerPath in analyzerPaths) - _project.AddAnalyzerReference(analyzerPath); + return RunAndReportNFWAsync(() => + { + foreach (var analyzerPath in analyzerPaths) + _project.AddAnalyzerReference(analyzerPath); + }, cancellationToken); } - public async Task AddDynamicFilesAsync(IReadOnlyList dynamicFilePaths, CancellationToken cancellationToken) + public Task AddDynamicFilesAsync(IReadOnlyList dynamicFilePaths, CancellationToken cancellationToken) { - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - foreach (var dynamicFilePath in dynamicFilePaths) - _project.AddDynamicSourceFile(dynamicFilePath, folders: ImmutableArray.Empty); + return RunAndReportNFWAsync(() => + { + foreach (var dynamicFilePath in dynamicFilePaths) + _project.AddDynamicSourceFile(dynamicFilePath, folders: []); + }, cancellationToken); } - public async Task AddMetadataReferencesAsync(IReadOnlyList metadataReferences, CancellationToken cancellationToken) + public Task AddMetadataReferencesAsync(IReadOnlyList metadataReferences, CancellationToken cancellationToken) { - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - foreach (var metadataReference in metadataReferences) - _project.AddMetadataReference(metadataReference.FilePath, metadataReference.CreateProperties()); + return RunAndReportNFWAsync(() => + { + foreach (var metadataReference in metadataReferences) + _project.AddMetadataReference(metadataReference.FilePath, metadataReference.CreateProperties()); + }, cancellationToken); } - public async Task AddSourceFilesAsync(IReadOnlyList sourceFiles, CancellationToken cancellationToken) + public Task AddSourceFilesAsync(IReadOnlyList sourceFiles, CancellationToken cancellationToken) { - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - foreach (var sourceFile in sourceFiles) - _project.AddSourceFile(sourceFile.FilePath, folders: [.. sourceFile.FolderNames]); + return RunAndReportNFWAsync(() => + { + foreach (var sourceFile in sourceFiles) + _project.AddSourceFile(sourceFile.FilePath, folders: [.. sourceFile.FolderNames]); + }, cancellationToken); } public void Dispose() @@ -93,145 +95,149 @@ public void Dispose() _project.RemoveFromWorkspace(); } - public async Task RemoveAdditionalFilesAsync(IReadOnlyList additionalFilePaths, CancellationToken cancellationToken) + public Task RemoveAdditionalFilesAsync(IReadOnlyList additionalFilePaths, CancellationToken cancellationToken) { - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - foreach (var additionalFilePath in additionalFilePaths) - _project.RemoveAdditionalFile(additionalFilePath); + return RunAndReportNFWAsync(() => + { + foreach (var additionalFilePath in additionalFilePaths) + _project.RemoveAdditionalFile(additionalFilePath); + }, cancellationToken); } - public async Task RemoveAnalyzerConfigFilesAsync(IReadOnlyList analyzerConfigPaths, CancellationToken cancellationToken) + public Task RemoveAnalyzerConfigFilesAsync(IReadOnlyList analyzerConfigPaths, CancellationToken cancellationToken) { - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - foreach (var analyzerConfigPath in analyzerConfigPaths) - _project.RemoveAnalyzerConfigFile(analyzerConfigPath); + return RunAndReportNFWAsync(() => + { + foreach (var analyzerConfigPath in analyzerConfigPaths) + _project.RemoveAnalyzerConfigFile(analyzerConfigPath); + }, cancellationToken); } - public async Task RemoveAnalyzerReferencesAsync(IReadOnlyList analyzerPaths, CancellationToken cancellationToken) + public Task RemoveAnalyzerReferencesAsync(IReadOnlyList analyzerPaths, CancellationToken cancellationToken) { - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - foreach (var analyzerPath in analyzerPaths) - _project.RemoveAnalyzerReference(analyzerPath); + return RunAndReportNFWAsync(() => + { + foreach (var analyzerPath in analyzerPaths) + _project.RemoveAnalyzerReference(analyzerPath); + }, cancellationToken); } - public async Task RemoveDynamicFilesAsync(IReadOnlyList dynamicFilePaths, CancellationToken cancellationToken) + public Task RemoveDynamicFilesAsync(IReadOnlyList dynamicFilePaths, CancellationToken cancellationToken) { - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - foreach (var dynamicFilePath in dynamicFilePaths) - _project.RemoveDynamicSourceFile(dynamicFilePath); + return RunAndReportNFWAsync(() => + { + foreach (var dynamicFilePath in dynamicFilePaths) + _project.RemoveDynamicSourceFile(dynamicFilePath); + }, cancellationToken); } - public async Task RemoveMetadataReferencesAsync(IReadOnlyList metadataReferences, CancellationToken cancellationToken) + public Task RemoveMetadataReferencesAsync(IReadOnlyList metadataReferences, CancellationToken cancellationToken) { - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - foreach (var metadataReference in metadataReferences) - _project.RemoveMetadataReference(metadataReference.FilePath, metadataReference.CreateProperties()); + return RunAndReportNFWAsync(() => + { + foreach (var metadataReference in metadataReferences) + _project.RemoveMetadataReference(metadataReference.FilePath, metadataReference.CreateProperties()); + }, cancellationToken); } - public async Task RemoveSourceFilesAsync(IReadOnlyList sourceFiles, CancellationToken cancellationToken) + public Task RemoveSourceFilesAsync(IReadOnlyList sourceFiles, CancellationToken cancellationToken) { - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - foreach (var sourceFile in sourceFiles) - _project.RemoveSourceFile(sourceFile); + return RunAndReportNFWAsync(() => + { + foreach (var sourceFile in sourceFiles) + _project.RemoveSourceFile(sourceFile); + }, cancellationToken); } - public async Task SetBuildSystemPropertiesAsync(IReadOnlyDictionary properties, CancellationToken cancellationToken) + public Task SetBuildSystemPropertiesAsync(IReadOnlyDictionary properties, CancellationToken cancellationToken) { - // Create a batch scope, just so we have asynchronous closing and application of the batch. - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - string? fileDirectory = null; - - foreach (var (name, value) in properties) + return RunAndReportNFWAsync(() => { - var valueOrNull = string.IsNullOrEmpty(value) ? null : value; + string? fileDirectory = null; - switch (name) + foreach (var (name, value) in properties) { - case "AssemblyName": _project.AssemblyName = value; break; - case "IntermediateAssembly": _project.CompilationOutputAssemblyFilePath = GetFullyQualifiedPath(valueOrNull); break; - case "MaxSupportedLangVersion": _project.MaxLangVersion = value; break; - case "RootNamespace": _project.DefaultNamespace = valueOrNull; break; - case "RunAnalyzers": _project.RunAnalyzers = bool.Parse(valueOrNull ?? bool.TrueString); break; - case "RunAnalyzersDuringLiveAnalysis": _project.RunAnalyzersDuringLiveAnalysis = bool.Parse(valueOrNull ?? bool.TrueString); break; - case "TargetPath": _project.OutputFilePath = GetFullyQualifiedPath(valueOrNull); break; - case "TargetRefPath": _project.OutputRefFilePath = GetFullyQualifiedPath(valueOrNull); break; - case "TargetFrameworkIdentifier": _targetFrameworkManager.UpdateIdentifierForProject(_project.Id, valueOrNull); break; + var valueOrNull = string.IsNullOrEmpty(value) ? null : value; + + switch (name) + { + case "AssemblyName": _project.AssemblyName = value; break; + case "IntermediateAssembly": _project.CompilationOutputAssemblyFilePath = GetFullyQualifiedPath(valueOrNull); break; + case "MaxSupportedLangVersion": _project.MaxLangVersion = value; break; + case "RootNamespace": _project.DefaultNamespace = valueOrNull; break; + case "RunAnalyzers": _project.RunAnalyzers = bool.Parse(valueOrNull ?? bool.TrueString); break; + case "RunAnalyzersDuringLiveAnalysis": _project.RunAnalyzersDuringLiveAnalysis = bool.Parse(valueOrNull ?? bool.TrueString); break; + case "TargetPath": _project.OutputFilePath = GetFullyQualifiedPath(valueOrNull); break; + case "TargetRefPath": _project.OutputRefFilePath = GetFullyQualifiedPath(valueOrNull); break; + case "TargetFrameworkIdentifier": _targetFrameworkManager.UpdateIdentifierForProject(_project.Id, valueOrNull); break; + } } - } - - string? GetFullyQualifiedPath(string? propertyValue) - { - Contract.ThrowIfNull(_project.FilePath, "We don't have a project path at this point."); - // Path.Combine doesn't check if the first parameter is an absolute path to a file instead of a directory, - // so make sure to use the directory from the _project.FilePath. If the propertyValue is an absolute - // path that will still be used, but if it's a relative path it will correctly construct the full path. - fileDirectory ??= Path.GetDirectoryName(_project.FilePath); - Contract.ThrowIfNull(fileDirectory); - - if (propertyValue is not null) - return Path.Combine(fileDirectory, propertyValue); - else - return null; - } + string? GetFullyQualifiedPath(string? propertyValue) + { + Contract.ThrowIfNull(_project.FilePath, "We don't have a project path at this point."); + + // Path.Combine doesn't check if the first parameter is an absolute path to a file instead of a directory, + // so make sure to use the directory from the _project.FilePath. If the propertyValue is an absolute + // path that will still be used, but if it's a relative path it will correctly construct the full path. + fileDirectory ??= Path.GetDirectoryName(_project.FilePath); + Contract.ThrowIfNull(fileDirectory); + + if (propertyValue is not null) + return Path.Combine(fileDirectory, propertyValue); + else + return null; + } + }, cancellationToken); } - public async Task SetCommandLineArgumentsAsync(IReadOnlyList arguments, CancellationToken cancellationToken) + public Task SetCommandLineArgumentsAsync(IReadOnlyList arguments, CancellationToken cancellationToken) { - // Create a batch scope, just so we have asynchronous closing and application of the batch. - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - _optionsProcessor.SetCommandLine([.. arguments]); + return RunAndReportNFWAsync(() => _optionsProcessor.SetCommandLine([.. arguments]), cancellationToken); } - public async Task SetDisplayNameAsync(string displayName, CancellationToken cancellationToken) + public Task SetDisplayNameAsync(string displayName, CancellationToken cancellationToken) { - // Create a batch scope, just so we have asynchronous closing and application of the batch. - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); + return RunAndReportNFWAsync(() => _project.DisplayName = displayName, cancellationToken); + } - _project.DisplayName = displayName; + public Task SetProjectHasAllInformationAsync(bool hasAllInformation, CancellationToken cancellationToken) + { + return RunAndReportNFWAsync(() => _project.HasAllInformation = hasAllInformation, cancellationToken); } - public async Task SetProjectHasAllInformationAsync(bool hasAllInformation, CancellationToken cancellationToken) + public async Task StartBatchAsync(CancellationToken cancellationToken) { // Create a batch scope, just so we have asynchronous closing and application of the batch. var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); await using var _ = disposableBatchScope.ConfigureAwait(false); - _project.HasAllInformation = hasAllInformation; + return new WorkspaceProjectBatch(_project.CreateBatchScope(), _logger); } - public async Task StartBatchAsync(CancellationToken cancellationToken) + private async Task RunAndReportNFWAsync(Action action, CancellationToken cancellationToken) { - var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); - await using var _ = disposableBatchScope.ConfigureAwait(false); - - return new WorkspaceProjectBatch(_project.CreateBatchScope()); + try + { + var disposableBatchScope = await _project.CreateBatchScopeAsync(cancellationToken).ConfigureAwait(false); + await using var _ = disposableBatchScope.ConfigureAwait(false); + action(); + } + catch (Exception e) when (LanguageServerFatalError.ReportAndLogAndPropagate(e, _logger, "Error applying project system update.")) + { + throw ExceptionUtilities.Unreachable(); + } } private class WorkspaceProjectBatch : IWorkspaceProjectBatch { private IAsyncDisposable? _batch; + private readonly ILogger _logger; - public WorkspaceProjectBatch(IAsyncDisposable batch) + public WorkspaceProjectBatch(IAsyncDisposable batch, ILogger logger) { _batch = batch; + _logger = logger; } public async Task ApplyAsync(CancellationToken cancellationToken) @@ -239,8 +245,15 @@ public async Task ApplyAsync(CancellationToken cancellationToken) if (_batch == null) throw new InvalidOperationException("The batch has already been applied."); - await _batch.DisposeAsync().ConfigureAwait(false); - _batch = null; + try + { + await _batch.DisposeAsync().ConfigureAwait(false); + _batch = null; + } + catch (Exception e) when (LanguageServerFatalError.ReportAndLogAndPropagate(e, _logger, "Error applying project system batch.")) + { + throw ExceptionUtilities.Unreachable(); + } } public void Dispose() diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProjectFactoryService.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProjectFactoryService.cs index c7a52e4807403..4bcbf018e089e 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProjectFactoryService.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/WorkspaceProjectFactoryService.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging; using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Shell.ServiceBroker; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; #pragma warning disable RS0030 // This is intentionally using System.ComponentModel.Composition for compatibility with MEF service broker. @@ -21,6 +22,7 @@ internal class WorkspaceProjectFactoryService : IWorkspaceProjectFactoryService, private readonly LanguageServerWorkspaceFactory _workspaceFactory; private readonly ProjectInitializationHandler _projectInitializationHandler; private readonly ILogger _logger; + private readonly ILoggerFactory _loggerFactory; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -28,6 +30,7 @@ public WorkspaceProjectFactoryService(LanguageServerWorkspaceFactory workspaceFa { _workspaceFactory = workspaceFactory; _projectInitializationHandler = projectInitializationHandler; + _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(nameof(WorkspaceProjectFactoryService)); } @@ -42,23 +45,30 @@ public async Task CreateAndAddProjectAsync(WorkspaceProjectCr { _logger.LogInformation(string.Format(LanguageServerResources.Project_0_loaded_by_CSharp_Dev_Kit, creationInfo.FilePath)); - if (creationInfo.BuildSystemProperties.TryGetValue("SolutionPath", out var solutionPath)) + try { - _workspaceFactory.ProjectSystemProjectFactory.SolutionPath = solutionPath; - } + if (creationInfo.BuildSystemProperties.TryGetValue("SolutionPath", out var solutionPath)) + { + _workspaceFactory.ProjectSystemProjectFactory.SolutionPath = solutionPath; + } - var project = await _workspaceFactory.ProjectSystemProjectFactory.CreateAndAddToWorkspaceAsync( - creationInfo.DisplayName, - creationInfo.Language, - new Workspaces.ProjectSystem.ProjectSystemProjectCreationInfo { FilePath = creationInfo.FilePath }, - _workspaceFactory.ProjectSystemHostInfo); + var project = await _workspaceFactory.ProjectSystemProjectFactory.CreateAndAddToWorkspaceAsync( + creationInfo.DisplayName, + creationInfo.Language, + new Workspaces.ProjectSystem.ProjectSystemProjectCreationInfo { FilePath = creationInfo.FilePath }, + _workspaceFactory.ProjectSystemHostInfo); - var workspaceProject = new WorkspaceProject(project, _workspaceFactory.Workspace.Services.SolutionServices, _workspaceFactory.TargetFrameworkManager); + var workspaceProject = new WorkspaceProject(project, _workspaceFactory.Workspace.Services.SolutionServices, _workspaceFactory.TargetFrameworkManager, _loggerFactory); - // We've created a new project, so initialize properties we have - await workspaceProject.SetBuildSystemPropertiesAsync(creationInfo.BuildSystemProperties, CancellationToken.None); + // We've created a new project, so initialize properties we have + await workspaceProject.SetBuildSystemPropertiesAsync(creationInfo.BuildSystemProperties, CancellationToken.None); - return workspaceProject; + return workspaceProject; + } + catch (Exception e) when (LanguageServerFatalError.ReportAndLogAndPropagate(e, _logger, $"Failed to create project {creationInfo.DisplayName}")) + { + throw ExceptionUtilities.Unreachable(); + } } public Task> GetSupportedBuildSystemPropertiesAsync(CancellationToken _) diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServerFatalError.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServerFatalError.cs new file mode 100644 index 0000000000000..de3ef431adc0c --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServerFatalError.cs @@ -0,0 +1,25 @@ +// 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.Diagnostics; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.LanguageServer; + +public class LanguageServerFatalError +{ + /// + /// Use in an exception filter to report an error without catching the exception. + /// Also logs the error using the provided logger. + /// + /// to avoid catching the exception. + [DebuggerHidden] + internal static bool ReportAndLogAndPropagate(Exception exception, ILogger logger, string logMessage, ErrorSeverity severity = ErrorSeverity.Uncategorized) + { + logger.LogError(exception, logMessage); + FatalError.ReportAndPropagate(exception, severity); + return false; + } +}