From 9b3fcc286b2385f21e73b78eed0d4d257d2f0d83 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Wed, 27 Nov 2019 14:16:34 -0800 Subject: [PATCH 1/8] Document load failures --- .../DiagnosticAnalyzerServiceTests.cs | 18 ++- .../Diagnostics/DiagnosticServiceTests.vb | 96 +++++++++++++ .../Portable/Diagnostics/AnalyzerHelper.cs | 20 ++- .../Analyzers/FileContentLoadAnalyzer.cs | 27 ++++ .../Diagnostics/Analyzers/IDEDiagnosticIds.cs | 2 + .../DefaultDiagnosticAnalyzerService.cs | 8 +- .../Diagnostics/DiagnosticResultSerializer.cs | 2 +- .../DiagnosticIncrementalAnalyzer.Executor.cs | 95 ++++++------ ...gnosticIncrementalAnalyzer.ProjectState.cs | 2 +- ...ementalAnalyzer.StateManager.HostStates.cs | 18 ++- ...ntalAnalyzer.StateManager.ProjectStates.cs | 4 +- ...gnosticIncrementalAnalyzer.StateManager.cs | 13 +- ...crementalAnalyzer_GetDiagnosticsForSpan.cs | 4 +- ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 5 +- .../Diagnostics/HostAnalyzerManager.cs | 2 +- .../DiagnosticAnalyzerExtensions.cs | 35 ++--- .../Diagnostics/DiagnosticAnalysisResult.cs | 4 +- .../ValuesSources/ConstantValueSource.cs | 7 +- .../Utilities/ValuesSources/ValueSource.cs | 15 +- .../Core/Portable/Workspace/FileTextLoader.cs | 2 + .../Workspace/Solution/DocumentState.cs | 10 ++ .../Solution/RecoverableTextAndVersion.cs | 37 ++--- .../SolutionState.CompilationTracker.cs | 18 ++- .../Workspace/Solution/TextAndVersion.cs | 31 +++- .../Workspace/Solution/TextDocumentState.cs | 120 +++------------- .../Portable/Workspace/Solution/TextLoader.cs | 135 +++++++++++++++--- .../WorkspaceDiagnosticDescriptors.cs | 23 +++ .../Portable/WorkspacesResources.Designer.cs | 18 +++ .../Core/Portable/WorkspacesResources.resx | 6 + .../Portable/xlf/WorkspacesResources.cs.xlf | 10 ++ .../Portable/xlf/WorkspacesResources.de.xlf | 10 ++ .../Portable/xlf/WorkspacesResources.es.xlf | 10 ++ .../Portable/xlf/WorkspacesResources.fr.xlf | 10 ++ .../Portable/xlf/WorkspacesResources.it.xlf | 10 ++ .../Portable/xlf/WorkspacesResources.ja.xlf | 10 ++ .../Portable/xlf/WorkspacesResources.ko.xlf | 10 ++ .../Portable/xlf/WorkspacesResources.pl.xlf | 10 ++ .../xlf/WorkspacesResources.pt-BR.xlf | 10 ++ .../Portable/xlf/WorkspacesResources.ru.xlf | 10 ++ .../Portable/xlf/WorkspacesResources.tr.xlf | 10 ++ .../xlf/WorkspacesResources.zh-Hans.xlf | 10 ++ .../xlf/WorkspacesResources.zh-Hant.xlf | 10 ++ .../CoreTest/SolutionTests/SolutionTests.cs | 67 ++------- .../MSBuildTest/MSBuildWorkspaceTests.cs | 2 +- 44 files changed, 653 insertions(+), 323 deletions(-) create mode 100644 src/Features/Core/Portable/Diagnostics/Analyzers/FileContentLoadAnalyzer.cs create mode 100644 src/Workspaces/Core/Portable/Workspace/WorkspaceDiagnosticDescriptors.cs diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 3f228631a5b1d..e6392ad029cfd 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -275,13 +275,17 @@ public void TestHostAnalyzerOrdering() var incrementalAnalyzer = (DiagnosticIncrementalAnalyzer)service.CreateIncrementalAnalyzer(workspace); var analyzers = incrementalAnalyzer.GetAnalyzersTestOnly(project).ToArray(); - Assert.Equal(typeof(CSharpCompilerDiagnosticAnalyzer), analyzers[0].GetType()); - Assert.Equal(typeof(Analyzer), analyzers[1].GetType()); - Assert.Equal(typeof(Priority0Analyzer), analyzers[2].GetType()); - Assert.Equal(typeof(Priority1Analyzer), analyzers[3].GetType()); - Assert.Equal(typeof(Priority10Analyzer), analyzers[4].GetType()); - Assert.Equal(typeof(Priority15Analyzer), analyzers[5].GetType()); - Assert.Equal(typeof(Priority20Analyzer), analyzers[6].GetType()); + AssertEx.Equal(new[] + { + typeof(FileContentLoadAnalyzer), + typeof(CSharpCompilerDiagnosticAnalyzer), + typeof(Analyzer), + typeof(Priority0Analyzer), + typeof(Priority1Analyzer), + typeof(Priority10Analyzer), + typeof(Priority15Analyzer), + typeof(Priority20Analyzer) + }, analyzers.Select(a => a.GetType())); } [Fact] diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 36c0b5d1eabab..b5268ef52a97c 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -1,6 +1,7 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. Imports System.Collections.Immutable +Imports System.IO Imports System.Reflection Imports System.Threading Imports System.Threading.Tasks @@ -27,6 +28,26 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Return New AnalyzerFileReference(fullPath, _assemblyLoader) End Function + Private Class FailingTextLoader + Inherits TextLoader + + Dim _path As String + + Friend Overrides ReadOnly Property FilePath As String + Get + Return _path + End Get + End Property + + Sub New(path As String) + _path = path + End Sub + + Public Overrides Function LoadTextAndVersionAsync(workspace As Workspace, documentId As DocumentId, cancellationToken As CancellationToken) As Task(Of TextAndVersion) + Throw New InvalidDataException("Bad data!") + End Function + End Class + Public Sub TestProjectAnalyzers() Dim test = @@ -515,6 +536,47 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests End Using End Sub + + Public Async Function TestDiagnosticAnalyzer_FileLoadFailure() As Task + Dim test = + + + class Goo { void M() {} } + + + + + Using workspace = TestWorkspace.CreateWorkspace(test) + Dim solution = workspace.CurrentSolution + Dim documentId = solution.Projects.Single().DocumentIds.Single() + solution = solution.WithDocumentTextLoader(documentId, New FailingTextLoader("Test.cs"), PreservationMode.PreserveIdentity) + workspace.ChangeSolution(solution) + + Dim project = solution.Projects.Single() + Dim document = project.Documents.Single() + + ' analyzer throws an exception + Dim analyzer = New CodeBlockStartedAnalyzer(Of Microsoft.CodeAnalysis.CSharp.SyntaxKind) + Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) + project = project.AddAnalyzerReference(analyzerReference) + + Dim exceptionDiagnosticsSource = New TestHostDiagnosticUpdateSource(workspace) + Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) + Dim diagnosticService = New TestDiagnosticAnalyzerService(hostDiagnosticUpdateSource:=exceptionDiagnosticsSource, mefExportProvider.GetExports(Of PrimaryWorkspace).Single.Value) + + Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) + Dim span = (Await document.GetSyntaxRootAsync().ConfigureAwait(False)).FullSpan + Dim diagnostics = Await diagnosticService.GetDiagnosticsForSpanAsync(document, span).ConfigureAwait(False) + Assert.Equal(1, diagnostics.Count()) + Assert.True(diagnostics(0).Id = "IDE1100") + Assert.Equal(String.Format(WorkspacesResources.Error_reading_source_file_content, "Test.cs", "Bad data!"), diagnostics(0).Message) + + ' analyzer should not be executed on a file that can't be loaded + diagnostics = exceptionDiagnosticsSource.GetTestAccessor().GetReportedDiagnostics(analyzer) + Assert.Empty(diagnostics) + End Using + End Function + Public Sub TestOperationAnalyzers() Dim test = @@ -817,6 +879,40 @@ class AnonymousFunctions End Using End Function + + Public Async Function TestStatefulCompilationAnalyzer_FileLoadFailure() As Task + Dim test = + + + class Goo { void M() {} } + + + + + Using workspace = TestWorkspace.CreateWorkspace(test) + Dim solution = workspace.CurrentSolution + Dim documentId = solution.Projects.Single().DocumentIds.Single() + solution = solution.WithDocumentTextLoader(documentId, New FailingTextLoader("Test.cs"), PreservationMode.PreserveIdentity) + workspace.ChangeSolution(solution) + + Dim project = solution.Projects.Single() + Dim document = project.Documents.Single() + + Dim analyzer = New StatefulCompilationAnalyzer + Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) + project = project.AddAnalyzerReference(analyzerReference) + + Dim projectDiagnostics = Await DiagnosticProviderTestUtilities.GetProjectDiagnosticsAsync(workspaceAnalyzerOpt:=Nothing, project:=project) + + ' The analyzer is invoked but the compilation does not contain a syntax tree that failed to load. + AssertEx.Equal( + { + "IDE1100: " & String.Format(WorkspacesResources.Error_reading_source_file_content, "Test.cs", "Bad data!"), + "StatefulCompilationAnalyzerDiagnostic: Compilation NamedType Count: 0" + }, projectDiagnostics.Select(Function(d) d.Id & ": " & d.GetMessage())) + End Using + End Function + Public Sub TestMultiplePartialDefinitionsInAFile() Dim test = diff --git a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs index 74acf15a31366..973d5168da339 100644 --- a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs +++ b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs @@ -50,7 +50,7 @@ internal static class AnalyzerHelper private const string AnalyzerExceptionDiagnosticCategory = "Intellisense"; public static bool IsWorkspaceDiagnosticAnalyzer(this DiagnosticAnalyzer analyzer) - => analyzer is DocumentDiagnosticAnalyzer || analyzer is ProjectDiagnosticAnalyzer; + => analyzer is DocumentDiagnosticAnalyzer || analyzer is ProjectDiagnosticAnalyzer || analyzer == FileContentLoadAnalyzer.Instance; public static bool IsBuiltInAnalyzer(this DiagnosticAnalyzer analyzer) => analyzer is IBuiltInAnalyzer || analyzer.IsWorkspaceDiagnosticAnalyzer() || analyzer.IsCompilerAnalyzer(); @@ -306,10 +306,19 @@ public static async Task> ComputeDiagnosticsAsync( Document document, DiagnosticAnalyzer analyzer, AnalysisKind kind, - TextSpan? spanOpt, + TextSpan? span, DiagnosticLogAggregator? logAggregator, CancellationToken cancellationToken) { + var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); + + if (analyzer == FileContentLoadAnalyzer.Instance || loadDiagnostic != null) + { + return (analyzer == FileContentLoadAnalyzer.Instance && loadDiagnostic != null) ? + SpecializedCollections.SingletonEnumerable(DiagnosticData.Create(document, loadDiagnostic)) : + SpecializedCollections.EmptyEnumerable(); + } + if (analyzer is DocumentDiagnosticAnalyzer documentAnalyzer) { var diagnostics = await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( @@ -373,7 +382,7 @@ public static async Task> ComputeDiagnosticsAsync( return SpecializedCollections.EmptyEnumerable(); } - diagnostics = await compilationWithAnalyzers.GetAnalyzerSemanticDiagnosticsAsync(model, spanOpt, singleAnalyzer, cancellationToken).ConfigureAwait(false); + diagnostics = await compilationWithAnalyzers.GetAnalyzerSemanticDiagnosticsAsync(model, span, singleAnalyzer, cancellationToken).ConfigureAwait(false); Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationWithAnalyzers.Compilation).Count()); return diagnostics.ConvertToLocalDiagnostics(document); @@ -385,7 +394,7 @@ public static async Task> ComputeDiagnosticsAsync( public static bool SupportAnalysisKind(this DiagnosticAnalyzerService service, DiagnosticAnalyzer analyzer, string language, AnalysisKind kind) { - // compiler diagnostic analyzer always support all kinds + // compiler diagnostic analyzer always supports all kinds: if (service.IsCompilerDiagnosticAnalyzer(language, analyzer)) { return true; @@ -395,7 +404,7 @@ public static bool SupportAnalysisKind(this DiagnosticAnalyzerService service, D { AnalysisKind.Syntax => analyzer.SupportsSyntaxDiagnosticAnalysis(), AnalysisKind.Semantic => analyzer.SupportsSemanticDiagnosticAnalysis(), - _ => Contract.FailWithReturn("shouldn't reach here"), + _ => throw ExceptionUtilities.UnexpectedValue(kind) }; } @@ -412,7 +421,6 @@ public static async Task> ComputeDocumentDiagnosticAnaly try { - var analyzeAsync = kind switch { AnalysisKind.Syntax => analyzer.AnalyzeSyntaxAsync(document, cancellationToken), diff --git a/src/Features/Core/Portable/Diagnostics/Analyzers/FileContentLoadAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/Analyzers/FileContentLoadAnalyzer.cs new file mode 100644 index 0000000000000..9a42c1639aaf3 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/Analyzers/FileContentLoadAnalyzer.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + /// + /// A dummy singleton analyzer. Its only purpose is to represent file content load failures in maps that are keyed by . + /// + internal sealed class FileContentLoadAnalyzer : DiagnosticAnalyzer + { + internal static readonly FileContentLoadAnalyzer Instance = new FileContentLoadAnalyzer(); + + private FileContentLoadAnalyzer() + { + } + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(WorkspaceDiagnosticDescriptors.ErrorReadingFileContent); + +#pragma warning disable RS1026 // Enable concurrent execution +#pragma warning disable RS1025 // Configure generated code analysis + public sealed override void Initialize(AnalysisContext context) { } +#pragma warning restore RS1025 // Configure generated code analysis +#pragma warning restore RS1026 // Enable concurrent execution + } +} diff --git a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs index 2d13ee4d60a70..32fb4148ed1d7 100644 --- a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs +++ b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs @@ -124,5 +124,7 @@ internal static class IDEDiagnosticIds public const string NamingRuleId = "IDE1006"; public const string UnboundIdentifierId = "IDE1007"; public const string UnboundConstructorId = "IDE1008"; + + // Reserved for workspace error ids IDE1100-IDE1200 (see WorkspaceDiagnosticDescriptors) } } diff --git a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs index 6f62480e19909..91a46bd22c3f3 100644 --- a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs @@ -146,6 +146,12 @@ private async Task AnalyzeForKind(Document document, AnalysisKind kind, Cancella private async Task> GetDiagnosticsAsync( Document document, AnalysisKind kind, CancellationToken cancellationToken) { + var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); + if (loadDiagnostic != null) + { + return ImmutableArray.Create(DiagnosticData.Create(document, loadDiagnostic)); + } + // given service must be DiagnosticAnalyzerService var diagnosticService = (DiagnosticAnalyzerService)_service._analyzerService; @@ -158,7 +164,7 @@ private async Task> GetDiagnosticsAsync( foreach (var analyzer in analyzers) { builder.AddRange(await diagnosticService.ComputeDiagnosticsAsync( - compilationWithAnalyzers, document, analyzer, kind, spanOpt: null, logAggregator: null, cancellationToken).ConfigureAwait(false)); + compilationWithAnalyzers, document, analyzer, kind, span: null, logAggregator: null, cancellationToken).ConfigureAwait(false)); } return builder.ToImmutableAndFree(); diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs index 890246c739483..4650a317e864e 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs @@ -71,7 +71,7 @@ public static DiagnosticAnalysisResultMap GetDocumentAnalysisDataAsync( return new DocumentAnalysisData(version, existingData.Items, ImmutableArray.Empty); } - var nullFilterSpan = (TextSpan?)null; - var diagnostics = await ComputeDiagnosticsAsync(compilation, document, stateSet.Analyzer, kind, nullFilterSpan, cancellationToken).ConfigureAwait(false); + var diagnostics = await AnalyzerService.ComputeDiagnosticsAsync(compilation, document, stateSet.Analyzer, kind, span: null, DiagnosticLogAggregator, cancellationToken).ConfigureAwait(false); // this is no-op in product. only run in test environment Logger.Log(functionId, (t, d, a, ds) => $"{GetDocumentLogMessage(t, d, a)}, {string.Join(Environment.NewLine, ds)}", @@ -165,12 +165,6 @@ private async Task> ComputeDiagnosticsAsync( - CompilationWithAnalyzers? compilation, Document document, DiagnosticAnalyzer analyzer, AnalysisKind kind, TextSpan? spanOpt, CancellationToken cancellationToken) - { - return AnalyzerService.ComputeDiagnosticsAsync(compilation, document, analyzer, kind, spanOpt, DiagnosticLogAggregator, cancellationToken); - } - /// /// Calculate all diagnostics for a given project using analyzers referenced by the project and specified IDE analyzers. /// @@ -304,34 +298,42 @@ private async Task> ComputeProjectDiagnosticAnalyzerDiagnosticsAsync( - Project project, - ProjectDiagnosticAnalyzer analyzer, - Compilation? compilation, - CancellationToken cancellationToken) + private async Task<(DiagnosticAnalysisResult loadDiagnostics, PooledHashSet? failedDocuments)> GetDocumentLoadFailuresAsync(Project project, VersionStamp version, CancellationToken cancellationToken) { - return AnalyzerService.ComputeProjectDiagnosticAnalyzerDiagnosticsAsync(project, analyzer, compilation, DiagnosticLogAggregator, cancellationToken); - } + PooledHashSet? failedDocuments = null; + ImmutableDictionary>.Builder? lazyLoadDiagnostics = null; - private Task> ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( - Document document, - DocumentDiagnosticAnalyzer analyzer, - AnalysisKind kind, - Compilation? compilation, - CancellationToken cancellationToken) - { - return AnalyzerService.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, analyzer, kind, compilation, DiagnosticLogAggregator, cancellationToken); + foreach (var document in project.Documents) + { + var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); + if (loadDiagnostic != null) + { + lazyLoadDiagnostics ??= ImmutableDictionary.CreateBuilder>(); + lazyLoadDiagnostics.Add(document.Id, ImmutableArray.Create(DiagnosticData.Create(project.Solution.Workspace, loadDiagnostic, project.Id))); + + failedDocuments ??= PooledHashSet.GetInstance(); + failedDocuments.Add(document); + } + } + + var result = DiagnosticAnalysisResult.Create( + project, + version, + syntaxLocalMap: lazyLoadDiagnostics?.ToImmutable() ?? ImmutableDictionary>.Empty, + semanticLocalMap: ImmutableDictionary>.Empty, + nonLocalMap: ImmutableDictionary>.Empty, + others: ImmutableArray.Empty, + documentIds: null); + + return (result, failedDocuments); } private void UpdateAnalyzerTelemetryData(ImmutableDictionary telemetry) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs index e1c763b8572d6..ef905dae4718f 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs @@ -509,7 +509,7 @@ private void Add(ref ImmutableDictionary>.Empty, _semanticLocals?.ToImmutable() ?? ImmutableDictionary>.Empty, _nonLocals?.ToImmutable() ?? ImmutableDictionary>.Empty, diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs index 2157380c28695..378dd812a04e4 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs @@ -23,7 +23,7 @@ static HostAnalyzerStateSets CreateLanguageSpecificAnalyzerMap(string language, { var analyzersPerReference = analyzerInfoCache.GetOrCreateHostDiagnosticAnalyzersPerReference(language); - var analyzerMap = CreateStateSetMap(analyzerInfoCache, language, analyzersPerReference.Values); + var analyzerMap = CreateStateSetMap(analyzerInfoCache, language, analyzersPerReference.Values, includeFileContentLoadAnalyzer: true); VerifyUniqueStateNames(analyzerMap.Values); return new HostAnalyzerStateSets(analyzerInfoCache, language, analyzerMap); @@ -34,6 +34,7 @@ static HostAnalyzerStateSets CreateLanguageSpecificAnalyzerMap(string language, private sealed class HostAnalyzerStateSets { + private const int FileContentLoadAnalyzerPriority = -3; private const int BuiltInCompilerPriority = -2; private const int RegularDiagnosticAnalyzerPriority = -1; @@ -69,16 +70,13 @@ private int GetPriority(StateSet state) return BuiltInCompilerPriority; } - switch (state.Analyzer) + return state.Analyzer switch { - case DocumentDiagnosticAnalyzer analyzer: - return Math.Max(0, analyzer.Priority); - case ProjectDiagnosticAnalyzer analyzer: - return Math.Max(0, analyzer.Priority); - default: - // regular analyzer get next priority after compiler analyzer - return RegularDiagnosticAnalyzerPriority; - } + FileContentLoadAnalyzer _ => FileContentLoadAnalyzerPriority, + DocumentDiagnosticAnalyzer analyzer => Math.Max(0, analyzer.Priority), + ProjectDiagnosticAnalyzer analyzer => Math.Max(0, analyzer.Priority), + _ => RegularDiagnosticAnalyzerPriority, + }; } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs index 2836da43ee5c5..4afe940afff36 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs @@ -78,7 +78,7 @@ private ImmutableDictionary CreateProjectStateSetM return ImmutableDictionary.Empty; } - return CreateStateSetMap(_analyzerInfoCache, project.Language, analyzersPerReference.Values); + return CreateStateSetMap(_analyzerInfoCache, project.Language, analyzersPerReference.Values, includeFileContentLoadAnalyzer: false); } private ImmutableDictionary GetOrUpdateProjectAnalyzerMap(Project project) @@ -90,7 +90,7 @@ private ImmutableDictionary GetOrUpdateProjectAnal private ImmutableDictionary UpdateProjectAnalyzerMap(Project project) { var newAnalyzersPerReference = _analyzerInfoCache.CreateProjectDiagnosticAnalyzersPerReference(project); - var newMap = CreateStateSetMap(_analyzerInfoCache, project.Language, newAnalyzersPerReference.Values); + var newMap = CreateStateSetMap(_analyzerInfoCache, project.Language, newAnalyzersPerReference.Values, includeFileContentLoadAnalyzer: false); RaiseProjectAnalyzerReferenceChangedIfNeeded(project, newAnalyzersPerReference, newMap); diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs index 80c8b729dd9d7..452e062be830e 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -261,15 +261,26 @@ private void RaiseProjectAnalyzerReferenceChanged(ProjectAnalyzerReferenceChange } private static ImmutableDictionary CreateStateSetMap( - DiagnosticAnalyzerInfoCache analyzerInfoCache, string language, IEnumerable> analyzerCollection) + DiagnosticAnalyzerInfoCache analyzerInfoCache, + string language, + IEnumerable> analyzerCollection, + bool includeFileContentLoadAnalyzer) { var compilerAnalyzer = analyzerInfoCache.GetCompilerDiagnosticAnalyzer(language); var builder = ImmutableDictionary.CreateBuilder(); + + if (includeFileContentLoadAnalyzer) + { + builder.Add(FileContentLoadAnalyzer.Instance, new StateSet(language, FileContentLoadAnalyzer.Instance, PredefinedBuildTools.Live)); + } + foreach (var analyzers in analyzerCollection) { foreach (var analyzer in analyzers) { + Debug.Assert(analyzer != FileContentLoadAnalyzer.Instance); + // TODO: // #1, all de-duplication should move to DiagnosticAnalyzerInfoCache // #2, not sure whether de-duplication of analyzer itself makes sense. this can only happen diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index d7b99bf1d62ff..6675d19352358 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -218,7 +218,7 @@ private async Task> GetCompilerSemanticDiagnosticsAs private Task> GetSyntaxDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) { - return _owner.ComputeDiagnosticsAsync(_compilation, _document, analyzer, AnalysisKind.Syntax, _range, cancellationToken); + return _owner.AnalyzerService.ComputeDiagnosticsAsync(_compilation, _document, analyzer, AnalysisKind.Syntax, _range, _owner.DiagnosticLogAggregator, cancellationToken); } private Task> GetSemanticDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) @@ -226,7 +226,7 @@ private Task> GetSemanticDiagnosticsAsync(Diagnostic var supportsSemanticInSpan = analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis(); var analysisSpan = supportsSemanticInSpan ? (TextSpan?)_range : null; - return _owner.ComputeDiagnosticsAsync(_compilation, _document, analyzer, AnalysisKind.Semantic, analysisSpan, cancellationToken); + return _owner.AnalyzerService.ComputeDiagnosticsAsync(_compilation, _document, analyzer, AnalysisKind.Semantic, analysisSpan, _owner.DiagnosticLogAggregator, cancellationToken); } private async Task> GetProjectDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 18eda63f01a37..ecd872a3f827d 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -304,8 +304,9 @@ private IEnumerable GetStateSetsForFullSolutionAnalysis(IEnumerable GetDiagnosticDescriptorsCore(Diagno public bool IsAnalyzerSuppressed(DiagnosticAnalyzer analyzer, Project project) { var options = project.CompilationOptions; - if (options == null || IsCompilerDiagnosticAnalyzer(project.Language, analyzer)) + if (options == null || analyzer == FileContentLoadAnalyzer.Instance || IsCompilerDiagnosticAnalyzer(project.Language, analyzer)) { return false; } diff --git a/src/Features/Core/Portable/Shared/Extensions/DiagnosticAnalyzerExtensions.cs b/src/Features/Core/Portable/Shared/Extensions/DiagnosticAnalyzerExtensions.cs index f6cd0b8684c46..33595011855bb 100644 --- a/src/Features/Core/Portable/Shared/Extensions/DiagnosticAnalyzerExtensions.cs +++ b/src/Features/Core/Portable/Shared/Extensions/DiagnosticAnalyzerExtensions.cs @@ -7,33 +7,16 @@ namespace Microsoft.CodeAnalysis.Shared.Extensions internal static class DiagnosticAnalyzerExtensions { public static DiagnosticAnalyzerCategory GetDiagnosticAnalyzerCategory(this DiagnosticAnalyzer analyzer) - { - var category = DiagnosticAnalyzerCategory.None; - - if (analyzer is DocumentDiagnosticAnalyzer) - { - category |= DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis | DiagnosticAnalyzerCategory.SemanticDocumentAnalysis; - } - else if (analyzer is ProjectDiagnosticAnalyzer) + => analyzer switch { - category |= DiagnosticAnalyzerCategory.ProjectAnalysis; - } - else - { - if (analyzer is IBuiltInAnalyzer builtInAnalyzer) - { - category = builtInAnalyzer.GetAnalyzerCategory(); - } - else - { - // It is not possible to know the categorization for a public analyzer, - // so return a worst-case categorization. - category = (DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis | DiagnosticAnalyzerCategory.SemanticDocumentAnalysis | DiagnosticAnalyzerCategory.ProjectAnalysis); - } - } - - return category; - } + FileContentLoadAnalyzer _ => DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis, + DocumentDiagnosticAnalyzer _ => DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis | DiagnosticAnalyzerCategory.SemanticDocumentAnalysis, + ProjectDiagnosticAnalyzer _ => DiagnosticAnalyzerCategory.ProjectAnalysis, + IBuiltInAnalyzer builtInAnalyzer => builtInAnalyzer.GetAnalyzerCategory(), + + // It is not possible to know the categorization for a public analyzer, so return a worst-case categorization. + _ => DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis | DiagnosticAnalyzerCategory.SemanticDocumentAnalysis | DiagnosticAnalyzerCategory.ProjectAnalysis + }; public static bool SupportsSyntaxDiagnosticAnalysis(this DiagnosticAnalyzer analyzer) { diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs index f94678acfaef9..f2c06a2b755cb 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs @@ -137,7 +137,7 @@ public static DiagnosticAnalysisResult CreateFromBuild(Project project, Immutabl return result; } - public static DiagnosticAnalysisResult CreateFromSerialization( + public static DiagnosticAnalysisResult Create( Project project, VersionStamp version, ImmutableDictionary> syntaxLocalMap, @@ -163,7 +163,7 @@ public static DiagnosticAnalysisResult CreateFromSerialization( public static DiagnosticAnalysisResult CreateFromBuilder(DiagnosticAnalysisResultBuilder builder) { - return CreateFromSerialization( + return Create( builder.Project, builder.Version, builder.SyntaxLocals, diff --git a/src/Workspaces/Core/Portable/Utilities/ValuesSources/ConstantValueSource.cs b/src/Workspaces/Core/Portable/Utilities/ValuesSources/ConstantValueSource.cs index 55b57ad79c4ae..7421b72a8f0ec 100644 --- a/src/Workspaces/Core/Portable/Utilities/ValuesSources/ConstantValueSource.cs +++ b/src/Workspaces/Core/Portable/Utilities/ValuesSources/ConstantValueSource.cs @@ -1,5 +1,8 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +#nullable enable + +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; @@ -11,7 +14,7 @@ namespace Roslyn.Utilities internal sealed class ConstantValueSource : ValueSource { private readonly T _value; - private Task _task; + private Task? _task; public ConstantValueSource(T value) { @@ -23,7 +26,7 @@ public override T GetValue(CancellationToken cancellationToken = default) return _value; } - public override bool TryGetValue(out T value) + public override bool TryGetValue([MaybeNullWhen(false)]out T value) { value = _value; return true; diff --git a/src/Workspaces/Core/Portable/Utilities/ValuesSources/ValueSource.cs b/src/Workspaces/Core/Portable/Utilities/ValuesSources/ValueSource.cs index ee7e4ec5ca06a..cfdc7341f4ac1 100644 --- a/src/Workspaces/Core/Portable/Utilities/ValuesSources/ValueSource.cs +++ b/src/Workspaces/Core/Portable/Utilities/ValuesSources/ValueSource.cs @@ -1,5 +1,8 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +#nullable enable + +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; @@ -10,18 +13,12 @@ namespace Roslyn.Utilities /// internal abstract class ValueSource { - public abstract bool TryGetValue(out T value); + public abstract bool TryGetValue([MaybeNullWhen(false)]out T value); public abstract T GetValue(CancellationToken cancellationToken = default); public abstract Task GetValueAsync(CancellationToken cancellationToken = default); - public bool HasValue - { - get - { - return this.TryGetValue(out var tmp); - } - } + public bool HasValue => TryGetValue(out _); - public static readonly ConstantValueSource Empty = new ConstantValueSource(default); + public static readonly ConstantValueSource Empty = new ConstantValueSource(default!); } } diff --git a/src/Workspaces/Core/Portable/Workspace/FileTextLoader.cs b/src/Workspaces/Core/Portable/Workspace/FileTextLoader.cs index c848705bf7fcf..d8b48c814e921 100644 --- a/src/Workspaces/Core/Portable/Workspace/FileTextLoader.cs +++ b/src/Workspaces/Core/Portable/Workspace/FileTextLoader.cs @@ -80,6 +80,8 @@ public FileTextLoader(string path, Encoding? defaultEncoding) DefaultEncoding = defaultEncoding; } + internal sealed override string? FilePath => Path; + protected virtual SourceText CreateText(Stream stream, Workspace workspace) { var factory = workspace.Services.GetRequiredService(); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index ffe54117645f8..08bb4bb327301 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -666,6 +666,16 @@ private static (ValueSource, TreeAndVersion) CreateRecoverableTe return (lazyTextAndVersion, TreeAndVersion.Create(tree, treeVersion)); } + internal override Task GetLoadDiagnosticAsync(CancellationToken cancellationToken) + { + if (TextAndVersionSource is TreeTextSource) + { + return Task.FromResult((Diagnostic?)null); + } + + return base.GetLoadDiagnosticAsync(cancellationToken); + } + private VersionStamp GetNewerVersion() { if (this.TextAndVersionSource.TryGetValue(out var textAndVersion)) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs b/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs index db2c97496e792..59f3a050a8e04 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs @@ -1,5 +1,8 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +#nullable enable + +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; @@ -16,12 +19,13 @@ internal class RecoverableTextAndVersion : ValueSource, ITextVer { private readonly ITemporaryStorageService _storageService; - private SemaphoreSlim _gateDoNotAccessDirectly; // Lazily created. Access via the Gate property - private ValueSource _initialSource; + private SemaphoreSlim? _lazyGate; + private ValueSource? _initialSource; - private RecoverableText _text; + private RecoverableText? _text; private VersionStamp _version; - private string _filePath; + private string? _filePath; + private Diagnostic? _loadDiagnostic; public RecoverableTextAndVersion( ValueSource initialTextAndVersion, @@ -31,20 +35,20 @@ public RecoverableTextAndVersion( _storageService = storageService; } - private SemaphoreSlim Gate => LazyInitialization.EnsureInitialized(ref _gateDoNotAccessDirectly, SemaphoreSlimFactory.Instance); + private SemaphoreSlim Gate => LazyInitialization.EnsureInitialized(ref _lazyGate, SemaphoreSlimFactory.Instance); - public ITemporaryTextStorage Storage => _text?.Storage; + public ITemporaryTextStorage? Storage => _text?.Storage; - public override bool TryGetValue(out TextAndVersion value) + public override bool TryGetValue([MaybeNullWhen(false)]out TextAndVersion value) { if (_text != null && _text.TryGetValue(out var text)) { - value = TextAndVersion.Create(text, _version, _filePath); + value = TextAndVersion.Create(text, _version, _filePath, _loadDiagnostic); return true; } else { - value = null; + value = null!; return false; } } @@ -57,7 +61,7 @@ public bool TryGetTextVersion(out VersionStamp version) // then try to get version from cached value. if (version == default) { - if (this.TryGetValue(out var textAndVersion)) + if (TryGetValue(out var textAndVersion)) { version = textAndVersion.Version; } @@ -74,12 +78,12 @@ public override TextAndVersion GetValue(CancellationToken cancellationToken = de { if (_text == null) { - return InitRecoverable(_initialSource.GetValue(cancellationToken)); + return InitRecoverable(_initialSource!.GetValue(cancellationToken)); } } } - return TextAndVersion.Create(_text.GetValue(cancellationToken), _version, _filePath); + return TextAndVersion.Create(_text.GetValue(cancellationToken), _version, _filePath, _loadDiagnostic); } public override async Task GetValueAsync(CancellationToken cancellationToken = default) @@ -90,13 +94,13 @@ public override async Task GetValueAsync(CancellationToken cance { if (_text == null) { - return InitRecoverable(await _initialSource.GetValueAsync(cancellationToken).ConfigureAwait(false)); + return InitRecoverable(await _initialSource!.GetValueAsync(cancellationToken).ConfigureAwait(false)); } } } var text = await _text.GetValueAsync(cancellationToken).ConfigureAwait(false); - return TextAndVersion.Create(text, _version, _filePath); + return TextAndVersion.Create(text, _version, _filePath, _loadDiagnostic); } private TextAndVersion InitRecoverable(TextAndVersion textAndVersion) @@ -104,6 +108,7 @@ private TextAndVersion InitRecoverable(TextAndVersion textAndVersion) _initialSource = null; _version = textAndVersion.Version; _filePath = textAndVersion.FilePath; + _loadDiagnostic = textAndVersion.LoadDiagnostic; _text = new RecoverableText(this, textAndVersion.Text); _text.GetValue(CancellationToken.None); // force access to trigger save return textAndVersion; @@ -112,7 +117,7 @@ private TextAndVersion InitRecoverable(TextAndVersion textAndVersion) private sealed class RecoverableText : RecoverableWeakValueSource { private readonly RecoverableTextAndVersion _parent; - private ITemporaryTextStorage _storage; + private ITemporaryTextStorage? _storage; public RecoverableText(RecoverableTextAndVersion parent, SourceText text) : base(new ConstantValueSource(text)) @@ -120,7 +125,7 @@ public RecoverableText(RecoverableTextAndVersion parent, SourceText text) _parent = parent; } - public ITemporaryTextStorage Storage => _storage; + public ITemporaryTextStorage? Storage => _storage; protected override async Task RecoverAsync(CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs index 24ce4d7d155ee..9ead030e24b72 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs @@ -497,17 +497,25 @@ private async Task BuildDeclarationCompilationFromScratchAsync( { var compilation = CreateEmptyCompilation(); - var trees = new SyntaxTree[ProjectState.DocumentIds.Count]; - var index = 0; + var trees = ArrayBuilder.GetInstance(ProjectState.DocumentIds.Count); foreach (var document in this.ProjectState.OrderedDocumentStates) { cancellationToken.ThrowIfCancellationRequested(); - trees[index] = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - index++; + + // Do not include syntax trees for documents whose content failed to load. + // Analyzers should not run on these (empty) syntax trees. + var loadDiagnostic = await document.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); + if (loadDiagnostic == null) + { + trees.Add(await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false)); + } } compilation = compilation.AddSyntaxTrees(trees); - this.WriteState(new FullDeclarationState(compilation), solution); + + trees.Free(); + + WriteState(new FullDeclarationState(compilation), solution); return compilation; } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextAndVersion.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextAndVersion.cs index f66ca519678b7..61970d496c9cd 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextAndVersion.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextAndVersion.cs @@ -23,32 +23,49 @@ public sealed class TextAndVersion public VersionStamp Version { get; } /// - /// An optional file path that identifies the origin of the source text + /// An optional file path that identifies the origin of the source text. Empty if not available. /// public string FilePath { get; } - private TextAndVersion(SourceText text, VersionStamp version, string? filePath) + /// + /// If an error occurred while loading the text the corresponding diagnostic, otherwise null. + /// + internal Diagnostic? LoadDiagnostic { get; } + + private TextAndVersion(SourceText text, VersionStamp version, string? filePath, Diagnostic? loadDiagnostic) { - this.Text = text; - this.Version = version; - this.FilePath = filePath ?? string.Empty; + Text = text; + Version = version; + FilePath = filePath ?? string.Empty; + LoadDiagnostic = loadDiagnostic; } /// - /// Create a new TextAndVersion instance. + /// Create a new instance. /// /// The text /// The version /// An optional file path that identifies the original of the source text. /// public static TextAndVersion Create(SourceText text, VersionStamp version, string? filePath = null) + => Create(text, version, filePath, loadDiagnostic: null); + + /// + /// Create a new instance. + /// + /// The text + /// The version + /// An optional file path that identifies the original of the source text. + /// Diagnostic describing failure to load the source text. + /// + internal static TextAndVersion Create(SourceText text, VersionStamp version, string? filePath, Diagnostic? loadDiagnostic) { if (text == null) { throw new ArgumentNullException(nameof(text)); } - return new TextAndVersion(text, version, filePath); + return new TextAndVersion(text, version, filePath, loadDiagnostic); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index 9202c11b9e9d8..1fd1ddd3b2c48 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. #nullable enable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -70,13 +69,13 @@ protected TextDocumentState( } public TextDocumentState(DocumentInfo info, SolutionServices services) - : this( - services, - info.DocumentServiceProvider, - info.Attributes, - sourceText: null, - textAndVersionSource: info.TextLoader != null - ? CreateRecoverableText(info.TextLoader, info.Id, services, reportInvalidDataException: false) + + : this(services, + info.DocumentServiceProvider, + info.Attributes, + sourceText: null, + textAndVersionSource: info.TextLoader != null + ? CreateRecoverableText(info.TextLoader, info.Id, services) : CreateStrongText(TextAndVersion.Create(SourceText.From(string.Empty, Encoding.UTF8), VersionStamp.Default, info.FilePath))) { } @@ -91,11 +90,11 @@ protected static ValueSource CreateStrongText(TextAndVersion tex return new ConstantValueSource(text); } - protected static ValueSource CreateStrongText(TextLoader loader, DocumentId documentId, SolutionServices services, bool reportInvalidDataException) + protected static ValueSource CreateStrongText(TextLoader loader, DocumentId documentId, SolutionServices services) { return new AsyncLazy( - asynchronousComputeFunction: c => LoadTextAsync(loader, documentId, services, reportInvalidDataException, c), - synchronousComputeFunction: c => LoadTextSynchronously(loader, documentId, services, reportInvalidDataException, c), + asynchronousComputeFunction: cancellationToken => loader.LoadTextAsync(services.Workspace, documentId, cancellationToken), + synchronousComputeFunction: cancellationToken => loader.LoadTextSynchronously(services.Workspace, documentId, cancellationToken), cacheResult: true); } @@ -104,102 +103,16 @@ protected static ValueSource CreateRecoverableText(TextAndVersio return new RecoverableTextAndVersion(CreateStrongText(text), services.TemporaryStorage); } - protected static ValueSource CreateRecoverableText(TextLoader loader, DocumentId documentId, SolutionServices services, bool reportInvalidDataException) + protected static ValueSource CreateRecoverableText(TextLoader loader, DocumentId documentId, SolutionServices services) { return new RecoverableTextAndVersion( new AsyncLazy( - asynchronousComputeFunction: c => LoadTextAsync(loader, documentId, services, reportInvalidDataException, c), - synchronousComputeFunction: c => LoadTextSynchronously(loader, documentId, services, reportInvalidDataException, c), + asynchronousComputeFunction: cancellationToken => loader.LoadTextAsync(services.Workspace, documentId, cancellationToken), + synchronousComputeFunction: cancellationToken => loader.LoadTextSynchronously(services.Workspace, documentId, cancellationToken), cacheResult: false), services.TemporaryStorage); } - private const double MaxDelaySecs = 1.0; - private const int MaxRetries = 5; - internal static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(MaxDelaySecs / MaxRetries); - - protected static async Task LoadTextAsync(TextLoader loader, DocumentId documentId, SolutionServices services, bool reportInvalidDataException, CancellationToken cancellationToken) - { - var retries = 0; - - while (true) - { - try - { - return await loader.LoadTextAndVersionAsync(services.Workspace, documentId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); - } - catch (OperationCanceledException) - { - // if load text is failed due to a cancellation, make sure we propagate it out to the caller - throw; - } - catch (IOException e) - { - if (++retries > MaxRetries) - { - services.Workspace.OnWorkspaceFailed(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, e.Message, documentId)); - return TextAndVersion.Create(SourceText.From(string.Empty, Encoding.UTF8), VersionStamp.Default, documentId.GetDebuggerDisplay()); - } - - // fall out to try again - } - catch (InvalidDataException e) - { - // TODO: Adjust this behavior in the future if we add support for non-text additional files - if (reportInvalidDataException) - { - services.Workspace.OnWorkspaceFailed(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, e.Message, documentId)); - } - - return TextAndVersion.Create(SourceText.From(string.Empty, Encoding.UTF8), VersionStamp.Default, documentId.GetDebuggerDisplay()); - } - - // try again after a delay - await Task.Delay(RetryDelay, cancellationToken).ConfigureAwait(false); - } - } - - protected static TextAndVersion LoadTextSynchronously(TextLoader loader, DocumentId documentId, SolutionServices services, bool reportInvalidDataException, CancellationToken cancellationToken) - { - var retries = 0; - - while (true) - { - try - { - return loader.LoadTextAndVersionSynchronously(services.Workspace, documentId, cancellationToken); - } - catch (OperationCanceledException) - { - // if load text is failed due to a cancellation, make sure we propagate it out to the caller - throw; - } - catch (IOException e) - { - if (++retries > MaxRetries) - { - services.Workspace.OnWorkspaceFailed(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, e.Message, documentId)); - return TextAndVersion.Create(SourceText.From(string.Empty, Encoding.UTF8), VersionStamp.Default, documentId.GetDebuggerDisplay()); - } - - // fall out to try again - } - catch (InvalidDataException e) - { - // TODO: Adjust this behavior in the future if we add support for non-text additional files - if (reportInvalidDataException) - { - services.Workspace.OnWorkspaceFailed(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, e.Message, documentId)); - } - - return TextAndVersion.Create(SourceText.From(string.Empty, Encoding.UTF8), VersionStamp.Default, documentId.GetDebuggerDisplay()); - } - - // try again after a delay - Thread.Sleep(RetryDelay); - } - } - public ITemporaryTextStorage? Storage { get @@ -331,8 +244,8 @@ public TextDocumentState UpdateText(TextLoader loader, PreservationMode mode) // don't blow up on non-text documents. var newTextSource = mode == PreservationMode.PreserveIdentity - ? CreateStrongText(loader, this.Id, this.solutionServices, reportInvalidDataException: false) - : CreateRecoverableText(loader, this.Id, this.solutionServices, reportInvalidDataException: false); + ? CreateStrongText(loader, Id, solutionServices) + : CreateRecoverableText(loader, Id, solutionServices); return UpdateText(newTextSource, mode, incremental: false); } @@ -359,6 +272,9 @@ private async Task GetTextAndVersionAsync(CancellationToken canc } } + internal virtual async Task GetLoadDiagnosticAsync(CancellationToken cancellationToken) + => (await GetTextAndVersionAsync(cancellationToken).ConfigureAwait(false)).LoadDiagnostic; + private VersionStamp GetNewerVersion() { if (this.TextAndVersionSource.TryGetValue(out var textAndVersion)) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs index 3b0e915a681b8..fc1af5374e3cf 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs @@ -1,6 +1,11 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +#nullable enable + using System; +using System.ComponentModel; +using System.IO; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Text; @@ -13,22 +18,120 @@ namespace Microsoft.CodeAnalysis /// public abstract class TextLoader { + private const double MaxDelaySecs = 1.0; + private const int MaxRetries = 5; + internal static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(MaxDelaySecs / MaxRetries); + + internal virtual string? FilePath => null; + /// - /// Load a text and a version of the document in the workspace. + /// Load a text and a version of the document. /// + /// + /// + /// public abstract Task LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken); /// /// Load a text and a version of the document in the workspace. /// + /// + /// + /// internal virtual TextAndVersion LoadTextAndVersionSynchronously(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) { // this implementation exists in case a custom derived type does not have access to internals return LoadTextAndVersionAsync(workspace, documentId, cancellationToken).WaitAndGetResult_CanCallOnBackground(cancellationToken); } + internal async Task LoadTextAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) + { + var retries = 0; + + while (true) + { + try + { + return await LoadTextAndVersionAsync(workspace, documentId, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); + } + catch (IOException e) + { + if (++retries > MaxRetries) + { + return CreateFailedText(workspace, documentId, e.Message); + } + + // fall out to try again + } + catch (InvalidDataException e) + { + return CreateFailedText(workspace, documentId, e.Message); + } + + // try again after a delay + await Task.Delay(RetryDelay, cancellationToken).ConfigureAwait(false); + } + } + + internal TextAndVersion LoadTextSynchronously(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) + { + var retries = 0; + + while (true) + { + try + { + return LoadTextAndVersionSynchronously(workspace, documentId, cancellationToken); + } + catch (IOException e) + { + if (++retries > MaxRetries) + { + return CreateFailedText(workspace, documentId, e.Message); + } + + // fall out to try again + } + catch (InvalidDataException e) + { + return CreateFailedText(workspace, documentId, e.Message); + } + + // try again after a delay + Thread.Sleep(RetryDelay); + } + } + + private TextAndVersion CreateFailedText(Workspace workspace, DocumentId documentId, string message) + { + // Notify workspace for backwards compatibility. + workspace.OnWorkspaceFailed(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, message, documentId)); + + Location location; + string display; + + var filePath = FilePath; + + if (filePath == null) + { + location = Location.None; + display = documentId.ToString(); + } + else + { + location = Location.Create(filePath, textSpan: default, lineSpan: default); + display = filePath; + } + + return TextAndVersion.Create( + SourceText.From(string.Empty, Encoding.UTF8), + VersionStamp.Default, + string.Empty, + Diagnostic.Create(WorkspaceDiagnosticDescriptors.ErrorReadingFileContent, location, new[] { display, message })); + } + /// - /// Creates a new TextLoader from an already existing source text and version. + /// Creates a new from an already existing source text and version. /// public static TextLoader From(TextAndVersion textAndVersion) { @@ -41,12 +144,12 @@ public static TextLoader From(TextAndVersion textAndVersion) } /// - /// Creates a TextLoader from a SourceTextContainer and version. + /// Creates a from a and version. /// /// The text obtained from the loader will be the current text of the container at the time /// the loader is accessed. /// - public static TextLoader From(SourceTextContainer container, VersionStamp version, string filePath = null) + public static TextLoader From(SourceTextContainer container, VersionStamp version, string? filePath = null) { if (container == null) { @@ -56,7 +159,7 @@ public static TextLoader From(SourceTextContainer container, VersionStamp versio return new TextContainerLoader(container, version, filePath); } - private class TextDocumentLoader : TextLoader + private sealed class TextDocumentLoader : TextLoader { private readonly TextAndVersion _textAndVersion; @@ -66,23 +169,19 @@ internal TextDocumentLoader(TextAndVersion textAndVersion) } public override Task LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) - { - return Task.FromResult(_textAndVersion); - } + => Task.FromResult(_textAndVersion); internal override TextAndVersion LoadTextAndVersionSynchronously(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) - { - return _textAndVersion; - } + => _textAndVersion; } - private class TextContainerLoader : TextLoader + private sealed class TextContainerLoader : TextLoader { private readonly SourceTextContainer _container; private readonly VersionStamp _version; - private readonly string _filePath; + private readonly string? _filePath; - internal TextContainerLoader(SourceTextContainer container, VersionStamp version, string filePath) + internal TextContainerLoader(SourceTextContainer container, VersionStamp version, string? filePath) { _container = container; _version = version; @@ -90,14 +189,10 @@ internal TextContainerLoader(SourceTextContainer container, VersionStamp version } public override Task LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) - { - return Task.FromResult(LoadTextAndVersionSynchronously(workspace, documentId, cancellationToken)); - } + => Task.FromResult(LoadTextAndVersionSynchronously(workspace, documentId, cancellationToken)); internal override TextAndVersion LoadTextAndVersionSynchronously(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) - { - return TextAndVersion.Create(_container.CurrentText, _version, _filePath); - } + => TextAndVersion.Create(_container.CurrentText, _version, _filePath); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/WorkspaceDiagnosticDescriptors.cs b/src/Workspaces/Core/Portable/Workspace/WorkspaceDiagnosticDescriptors.cs new file mode 100644 index 0000000000000..6571da3eb53ff --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/WorkspaceDiagnosticDescriptors.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis +{ + internal sealed class WorkspaceDiagnosticDescriptors + { + internal readonly static DiagnosticDescriptor ErrorReadingFileContent; + + internal const string ErrorReadingFileContentId = "IDE1100"; + + static WorkspaceDiagnosticDescriptors() + { + ErrorReadingFileContent = new DiagnosticDescriptor( + id: ErrorReadingFileContentId, + title: new LocalizableResourceString(nameof(WorkspacesResources.Workspace_error), WorkspacesResources.ResourceManager, typeof(WorkspacesResources)), + messageFormat: new LocalizableResourceString(nameof(WorkspacesResources.Error_reading_source_file_content), WorkspacesResources.ResourceManager, typeof(WorkspacesResources)), + category: WorkspacesResources.Workspace_error, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + customTags: new[] { WellKnownDiagnosticTags.NotConfigurable }); + } + } +} diff --git a/src/Workspaces/Core/Portable/WorkspacesResources.Designer.cs b/src/Workspaces/Core/Portable/WorkspacesResources.Designer.cs index 574ae2175afb3..29f9ccd8d60f7 100644 --- a/src/Workspaces/Core/Portable/WorkspacesResources.Designer.cs +++ b/src/Workspaces/Core/Portable/WorkspacesResources.Designer.cs @@ -781,6 +781,15 @@ internal static string Error { } } + /// + /// Looks up a localized string similar to Error reading content of source file '{0}' -- '{1}'.. + /// + internal static string Error_reading_source_file_content { + get { + return ResourceManager.GetString("Error_reading_source_file_content", resourceCulture); + } + } + /// /// Looks up a localized string similar to Event. /// @@ -3800,6 +3809,15 @@ internal static string Warning_adding_imports_will_bring_an_extension_method_int } } + /// + /// Looks up a localized string similar to Workspace error.. + /// + internal static string Workspace_error { + get { + return ResourceManager.GetString("Workspace_error", resourceCulture); + } + } + /// /// Looks up a localized string similar to Workspace is not empty.. /// diff --git a/src/Workspaces/Core/Portable/WorkspacesResources.resx b/src/Workspaces/Core/Portable/WorkspacesResources.resx index edd481890d2f2..ef79fc9254f58 100644 --- a/src/Workspaces/Core/Portable/WorkspacesResources.resx +++ b/src/Workspaces/Core/Portable/WorkspacesResources.resx @@ -168,6 +168,12 @@ Adding projects is not supported. + + Workspace error + + + Error reading content of source file '{0}' -- '{1}'. + Workspace is not empty. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf index 0f3503752abe0..416f5eeabcc9b 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf @@ -52,6 +52,11 @@ Document does not support syntax trees + + Error reading content of source file '{0}' -- '{1}'. + Error reading content of source file '{0}' -- '{1}'. + + Expression-level preferences Předvolby na úrovni výrazů @@ -1342,6 +1347,11 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou šířkou se o Adding imports will bring an extension method into scope with the same name as '{0}' + + Workspace error + Workspace error + + Workspace is not empty. Pracovní prostor není platný. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf index 6cc0b420f37fa..e864596bf427e 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf @@ -52,6 +52,11 @@ Document does not support syntax trees + + Error reading content of source file '{0}' -- '{1}'. + Error reading content of source file '{0}' -- '{1}'. + + Expression-level preferences Einstellungen für Ausdrucksebene @@ -1342,6 +1347,11 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg Adding imports will bring an extension method into scope with the same name as '{0}' + + Workspace error + Workspace error + + Workspace is not empty. Arbeitsbereich ist nicht leer. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf index 099a58d5d1b8f..effee74b12259 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf @@ -52,6 +52,11 @@ Document does not support syntax trees + + Error reading content of source file '{0}' -- '{1}'. + Error reading content of source file '{0}' -- '{1}'. + + Expression-level preferences Preferencias de nivel de expresión @@ -1342,6 +1347,11 @@ Las aserciones posteriores positivas de ancho cero se usan normalmente al princi Adding imports will bring an extension method into scope with the same name as '{0}' + + Workspace error + Workspace error + + Workspace is not empty. El área de trabajo no está vacía. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf index 71b4813701ab7..2a0fe52c58e1a 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf @@ -52,6 +52,11 @@ Document does not support syntax trees + + Error reading content of source file '{0}' -- '{1}'. + Error reading content of source file '{0}' -- '{1}'. + + Expression-level preferences Préférences de niveau expression @@ -1342,6 +1347,11 @@ Les assertions de postanalyse positives de largeur nulle sont généralement uti Adding imports will bring an extension method into scope with the same name as '{0}' + + Workspace error + Workspace error + + Workspace is not empty. L'espace de travail n'est pas vide. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf index 49bc2c04835dd..b1b27653b4183 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf @@ -52,6 +52,11 @@ Document does not support syntax trees + + Error reading content of source file '{0}' -- '{1}'. + Error reading content of source file '{0}' -- '{1}'. + + Expression-level preferences Preferenze a livello di espressione @@ -1342,6 +1347,11 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all' Adding imports will bring an extension method into scope with the same name as '{0}' + + Workspace error + Workspace error + + Workspace is not empty. L'area di lavoro non è vuota. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf index b5d4d039bea89..8fd7b5f3354e6 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf @@ -52,6 +52,11 @@ Document does not support syntax trees + + Error reading content of source file '{0}' -- '{1}'. + Error reading content of source file '{0}' -- '{1}'. + + Expression-level preferences 式レベルの設定 @@ -1342,6 +1347,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Adding imports will bring an extension method into scope with the same name as '{0}' + + Workspace error + Workspace error + + Workspace is not empty. ワークスペースが空ではありません。 diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf index 537e108968c24..dfff156da133c 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf @@ -52,6 +52,11 @@ Document does not support syntax trees + + Error reading content of source file '{0}' -- '{1}'. + Error reading content of source file '{0}' -- '{1}'. + + Expression-level preferences 식 수준 기본 설정 @@ -1342,6 +1347,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Adding imports will bring an extension method into scope with the same name as '{0}' + + Workspace error + Workspace error + + Workspace is not empty. 작업 영역이 비어 있지 않습니다. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf index a060c904723a9..0edb692181ca8 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf @@ -52,6 +52,11 @@ Document does not support syntax trees + + Error reading content of source file '{0}' -- '{1}'. + Error reading content of source file '{0}' -- '{1}'. + + Expression-level preferences Preferencje na poziomie wyrażeń @@ -1342,6 +1347,11 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk Adding imports will bring an extension method into scope with the same name as '{0}' + + Workspace error + Workspace error + + Workspace is not empty. Obszar roboczy nie jest pusty. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf index 94aa24a5fd360..b5f8885e5efe8 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf @@ -52,6 +52,11 @@ Document does not support syntax trees + + Error reading content of source file '{0}' -- '{1}'. + Error reading content of source file '{0}' -- '{1}'. + + Expression-level preferences Preferências de nível de expressão @@ -1342,6 +1347,11 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas Adding imports will bring an extension method into scope with the same name as '{0}' + + Workspace error + Workspace error + + Workspace is not empty. Workspace não está vazio. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf index 06d470019952f..50a4995e32aa6 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf @@ -52,6 +52,11 @@ Document does not support syntax trees + + Error reading content of source file '{0}' -- '{1}'. + Error reading content of source file '{0}' -- '{1}'. + + Expression-level preferences Выражения уровень предпочтения @@ -1342,6 +1347,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Adding imports will bring an extension method into scope with the same name as '{0}' + + Workspace error + Workspace error + + Workspace is not empty. Рабочая область не пуста. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf index b20f11e0be072..eb2c92e6ac088 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf @@ -52,6 +52,11 @@ Document does not support syntax trees + + Error reading content of source file '{0}' -- '{1}'. + Error reading content of source file '{0}' -- '{1}'. + + Expression-level preferences İfade düzey tercihleri @@ -1342,6 +1347,11 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri Adding imports will bring an extension method into scope with the same name as '{0}' + + Workspace error + Workspace error + + Workspace is not empty. Çalışma alanı boş değil. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf index 7d5295d23d170..577562c52e283 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf @@ -52,6 +52,11 @@ Document does not support syntax trees + + Error reading content of source file '{0}' -- '{1}'. + Error reading content of source file '{0}' -- '{1}'. + + Expression-level preferences 表达式级首选项 @@ -1342,6 +1347,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Adding imports will bring an extension method into scope with the same name as '{0}' + + Workspace error + Workspace error + + Workspace is not empty. 工作区不为空。 diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf index 4d231d261ccb7..a230e0b4d71ef 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf @@ -52,6 +52,11 @@ Document does not support syntax trees + + Error reading content of source file '{0}' -- '{1}'. + Error reading content of source file '{0}' -- '{1}'. + + Expression-level preferences 運算式層級喜好設定 @@ -1342,6 +1347,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Adding imports will bring an extension method into scope with the same name as '{0}' + + Workspace error + Workspace error + + Workspace is not empty. 工作區不是空的。 diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index af3fe6ea01c18..9493d61f8662e 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -1242,76 +1242,27 @@ public TestLanguageServiceB() } } - [Fact] - public void TestDocumentFileAccessFailureMissingFile() - { - var solution = new AdhocWorkspace().CurrentSolution; - - WorkspaceDiagnostic diagnostic = null; - - solution.Workspace.WorkspaceFailed += (sender, args) => - { - diagnostic = args.Diagnostic; - }; - - var pid = ProjectId.CreateNewId(); - var did = DocumentId.CreateNewId(pid); - - solution = solution.AddProject(pid, "goo", "goo", LanguageNames.CSharp) - .AddDocument(did, "x", new FileTextLoader(@"C:\doesnotexist.cs", Encoding.UTF8)); - - var doc = solution.GetDocument(did); - var text = doc.GetTextAsync().Result; - - WaitFor(() => diagnostic != null, TimeSpan.FromSeconds(5)); - - Assert.NotNull(diagnostic); - var dd = diagnostic as DocumentDiagnostic; - Assert.NotNull(dd); - Assert.Equal(did, dd.DocumentId); - Assert.Equal(WorkspaceDiagnosticKind.Failure, dd.Kind); - } - [Fact] [WorkItem(666263, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/666263")] - public void TestWorkspaceDiagnosticHasDebuggerText() + public async Task TestDocumentFileAccessFailureMissingFile() { - var solution = new AdhocWorkspace().CurrentSolution; - - WorkspaceDiagnostic diagnostic = null; - - solution.Workspace.WorkspaceFailed += (sender, args) => - { - diagnostic = args.Diagnostic; - }; + var workspace = new AdhocWorkspace(); + var solution = workspace.CurrentSolution; var pid = ProjectId.CreateNewId(); var did = DocumentId.CreateNewId(pid); solution = solution.AddProject(pid, "goo", "goo", LanguageNames.CSharp) - .AddDocument(did, "x", new FileTextLoader(@"C:\doesnotexist.cs", Encoding.UTF8)); + .AddDocument(did, "x", new FileTextLoader(@"C:\doesnotexist.cs", Encoding.UTF8)) + .WithDocumentFilePath(did, "document path"); var doc = solution.GetDocument(did); - var text = doc.GetTextAsync().Result; - - WaitFor(() => diagnostic != null, TimeSpan.FromSeconds(5)); + var text = await doc.GetTextAsync().ConfigureAwait(false); - Assert.NotNull(diagnostic); - var dd = diagnostic as DocumentDiagnostic; - Assert.NotNull(dd); - Assert.Equal(dd.ToString(), string.Format("[{0}] {1}", WorkspacesResources.Failure, dd.Message)); - } - - private bool WaitFor(Func condition, TimeSpan timeout) - { - var start = DateTime.UtcNow; - - while ((DateTime.UtcNow - start) < timeout && !condition()) - { - Thread.Sleep(TimeSpan.FromMilliseconds(10)); - } + var diagnostic = await doc.State.GetLoadDiagnosticAsync(CancellationToken.None).ConfigureAwait(false); - return condition(); + Assert.Equal(@"C:\doesnotexist.cs: (0,0)-(0,0)", diagnostic.Location.GetLineSpan().ToString()); + Assert.Equal("", text.ToString()); } [Fact] diff --git a/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs b/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs index 3e30599ddf99c..35453b332c932 100644 --- a/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs +++ b/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs @@ -964,7 +964,7 @@ public async Task TestOpenSolution_WithTemporaryLockedFile_SucceedsWithoutFailur var getTextTask = doc.GetTextAsync(); // wait 1 unit of retry delay then close file - var delay = TextDocumentState.RetryDelay; + var delay = TextLoader.RetryDelay; await Task.Delay(delay).ContinueWith(t => file.Close(), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); // finish reading text From ffa04657942699cc16e63a48aad3195fbb16d4df Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Wed, 27 Nov 2019 14:22:35 -0800 Subject: [PATCH 2/8] Delete WorkspaceFailureOutputPane --- .../Workspace/WorkspaceFailureOutputPane.cs | 114 ------------------ src/VisualStudio/Core/Def/RoslynPackage.cs | 4 - 2 files changed, 118 deletions(-) delete mode 100644 src/VisualStudio/Core/Def/Implementation/Workspace/WorkspaceFailureOutputPane.cs diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/WorkspaceFailureOutputPane.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/WorkspaceFailureOutputPane.cs deleted file mode 100644 index ffc4ff581476a..0000000000000 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/WorkspaceFailureOutputPane.cs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.VisualStudio.Shell.Interop; - -namespace Microsoft.VisualStudio.LanguageServices -{ - using Workspace = Microsoft.CodeAnalysis.Workspace; - - internal class WorkspaceFailureOutputPane : ForegroundThreadAffinitizedObject - { - private static readonly Guid s_workspacePaneGuid = new Guid("53D7CABD-085E-46AF-ACCA-EF5A640641CA"); - - private readonly IServiceProvider _serviceProvider; - private readonly Workspace _workspace; - - public WorkspaceFailureOutputPane(IThreadingContext threadingContext, IServiceProvider serviceProvider, Workspace workspace) - : base(threadingContext) - { - _serviceProvider = serviceProvider; - _workspace = workspace; - _workspace.WorkspaceFailed += OnWorkspaceFailed; - } - - private void OnWorkspaceFailed(object sender, WorkspaceDiagnosticEventArgs e) - { - InvokeBelowInputPriorityAsync(() => - { - this.OutputPaneOpt?.OutputString(e.Diagnostic.ToString() + Environment.NewLine); - }); - } - - private IVsOutputWindowPane _doNotAccessDirectlyOutputPane; - - private IVsOutputWindowPane OutputPaneOpt - { - get - { - AssertIsForeground(); - - if (_doNotAccessDirectlyOutputPane == null) - { - var outputWindow = (IVsOutputWindow)_serviceProvider.GetService(typeof(SVsOutputWindow)); - - // This may run during the shutdown of Visual Studio and so we must be ready for the service - // not being available. - if (outputWindow == null) - { - return null; - } - - // Output Window panes have two states; initialized and active. The former is used to indicate that the pane - // can be made active ("selected") by the user, the latter indicates that the pane is currently active. - // There's no way to only initialize a pane without also making it active so we remember the last active pane - // and reactivate it after we've created ourselves to avoid stealing focus away from it. - var lastActivePane = GetActivePane(outputWindow); - - _doNotAccessDirectlyOutputPane = CreateOutputPane(outputWindow); - - if (lastActivePane != Guid.Empty) - { - ActivatePane(outputWindow, lastActivePane); - } - } - - return _doNotAccessDirectlyOutputPane; - } - } - - private IVsOutputWindowPane CreateOutputPane(IVsOutputWindow outputWindow) - { - AssertIsForeground(); - - // Try to get the workspace pane if it has already been registered - var workspacePaneGuid = s_workspacePaneGuid; - - // If the pane has already been created, CreatePane returns it - if (ErrorHandler.Succeeded(outputWindow.CreatePane(ref workspacePaneGuid, ServicesVSResources.IntelliSense, fInitVisible: 1, fClearWithSolution: 1)) && - ErrorHandler.Succeeded(outputWindow.GetPane(ref workspacePaneGuid, out var pane))) - { - return pane; - } - - return null; - } - - private Guid GetActivePane(IVsOutputWindow outputWindow) - { - AssertIsForeground(); - - if (outputWindow is IVsOutputWindow2 outputWindow2) - { - if (ErrorHandler.Succeeded(outputWindow2.GetActivePaneGUID(out var activePaneGuid))) - { - return activePaneGuid; - } - } - - return Guid.Empty; - } - - private void ActivatePane(IVsOutputWindow outputWindow, Guid paneGuid) - { - AssertIsForeground(); - - if (ErrorHandler.Succeeded(outputWindow.GetPane(ref paneGuid, out var pane))) - { - pane.Activate(); - } - } - } -} diff --git a/src/VisualStudio/Core/Def/RoslynPackage.cs b/src/VisualStudio/Core/Def/RoslynPackage.cs index 157ebdaeb2822..5eb4b977af814 100644 --- a/src/VisualStudio/Core/Def/RoslynPackage.cs +++ b/src/VisualStudio/Core/Def/RoslynPackage.cs @@ -42,7 +42,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Setup internal class RoslynPackage : AbstractPackage { private VisualStudioWorkspace _workspace; - private WorkspaceFailureOutputPane _outputPane; private IComponentModel _componentModel; private RuleSetEventHandler _ruleSetEventHandler; private IDisposable _solutionEventMonitor; @@ -76,9 +75,6 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke RoslynTelemetrySetup.Initialize(this); - // set workspace output pane - _outputPane = new WorkspaceFailureOutputPane(_componentModel.GetService(), this, _workspace); - InitializeColors(); // load some services that have to be loaded in UI thread From 9fc17fd71d4f67aedbccbc2c651c2595295df680 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Wed, 27 Nov 2019 14:26:51 -0800 Subject: [PATCH 3/8] Remove dead code --- src/Workspaces/CoreTestUtilities/WorkspaceExtensions.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Workspaces/CoreTestUtilities/WorkspaceExtensions.cs b/src/Workspaces/CoreTestUtilities/WorkspaceExtensions.cs index 0930ee5425f05..fcb3917d28743 100644 --- a/src/Workspaces/CoreTestUtilities/WorkspaceExtensions.cs +++ b/src/Workspaces/CoreTestUtilities/WorkspaceExtensions.cs @@ -60,12 +60,5 @@ internal static EventWaiter VerifyWorkspaceChangedEvent(this Workspace workspace workspace.WorkspaceChanged += wew.Wrap((sender, args) => action(args)); return wew; } - - internal static EventWaiter VerifyWorkspaceFailedEvent(this Workspace workspace, Action action) - { - var wew = new EventWaiter(); - workspace.WorkspaceFailed += wew.Wrap((sender, args) => action(args)); - return wew; - } } } From ca357e4282cb17c7477ae17581ef922e5a60ce2c Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Wed, 27 Nov 2019 18:37:21 -0800 Subject: [PATCH 4/8] MSBuildWorkspace cleanup --- .../MSBuild/MSBuild/DiagnosticReporter.cs | 6 +- .../MSBuild/MSBuildProjectLoader.Worker.cs | 10 +-- .../MSBuild/MSBuild/MSBuildProjectLoader.cs | 17 ++-- .../Core/MSBuild/MSBuild/MSBuildWorkspace.cs | 29 +++---- .../MSBuild/ProjectFile/ProjectFileLoader.cs | 5 +- .../ProjectFile/ProjectFileLoaderRegistry.cs | 14 +-- .../MSBuildTest/MSBuildWorkspaceTests.cs | 85 +++---------------- 7 files changed, 53 insertions(+), 113 deletions(-) diff --git a/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs b/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs index 8f9659413b4d1..59dad12804055 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs @@ -1,17 +1,20 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.MSBuild.Logging; namespace Microsoft.CodeAnalysis.MSBuild { - internal class DiagnosticReporter + internal sealed class DiagnosticReporter { + internal ImmutableList Diagnostics; private readonly Workspace _workspace; public DiagnosticReporter(Workspace workspace) { _workspace = workspace; + Diagnostics = ImmutableList.Empty; } public void Report(DiagnosticReportingMode mode, string message, Func createException = null) @@ -40,6 +43,7 @@ public void Report(DiagnosticReportingMode mode, string message, Func d.Add(diagnostic)); _workspace.OnWorkspaceFailed(diagnostic); } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs index f7facfc2c582e..42340eea78638 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.Worker.cs @@ -21,7 +21,7 @@ public partial class MSBuildProjectLoader { private partial class Worker { - private readonly Workspace _workspace; + private readonly HostWorkspaceServices _workspaceServices; private readonly DiagnosticReporter _diagnosticReporter; private readonly PathResolver _pathResolver; private readonly ProjectFileLoaderRegistry _projectFileLoaderRegistry; @@ -70,7 +70,7 @@ private partial class Worker private readonly Dictionary> _pathToDiscoveredProjectInfosMap; public Worker( - Workspace workspace, + HostWorkspaceServices services, DiagnosticReporter diagnosticReporter, PathResolver pathResolver, ProjectFileLoaderRegistry projectFileLoaderRegistry, @@ -84,7 +84,7 @@ public Worker( DiagnosticReportingOptions discoveredProjectOptions, bool preferMetadataForReferencesOfDiscoveredProjects) { - _workspace = workspace; + _workspaceServices = services; _diagnosticReporter = diagnosticReporter; _pathResolver = pathResolver; _projectFileLoaderRegistry = projectFileLoaderRegistry; @@ -494,13 +494,13 @@ private void CheckForDuplicateDocuments(ImmutableArray documents, private TLanguageService GetLanguageService(string languageName) where TLanguageService : ILanguageService - => _workspace.Services + => _workspaceServices .GetLanguageServices(languageName) .GetService(); private TWorkspaceService GetWorkspaceService() where TWorkspaceService : IWorkspaceService - => _workspace.Services + => _workspaceServices .GetService(); } } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs index e263db71796c1..822a01b53c967 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs @@ -5,6 +5,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.MSBuild.Build; using Roslyn.Utilities; using MSB = Microsoft.Build; @@ -17,7 +18,7 @@ namespace Microsoft.CodeAnalysis.MSBuild public partial class MSBuildProjectLoader { // the workspace that the projects and solutions are intended to be loaded into. - private readonly Workspace _workspace; + private readonly HostWorkspaceServices _workspaceServices; private readonly DiagnosticReporter _diagnosticReporter; private readonly PathResolver _pathResolver; @@ -28,15 +29,15 @@ public partial class MSBuildProjectLoader private ImmutableDictionary _properties; internal MSBuildProjectLoader( - Workspace workspace, + HostWorkspaceServices workspaceServices, DiagnosticReporter diagnosticReporter, ProjectFileLoaderRegistry projectFileLoaderRegistry, ImmutableDictionary properties) { - _workspace = workspace; - _diagnosticReporter = diagnosticReporter ?? new DiagnosticReporter(workspace); + _workspaceServices = workspaceServices; + _diagnosticReporter = diagnosticReporter; _pathResolver = new PathResolver(_diagnosticReporter); - _projectFileLoaderRegistry = projectFileLoaderRegistry ?? new ProjectFileLoaderRegistry(workspace, _diagnosticReporter); + _projectFileLoaderRegistry = projectFileLoaderRegistry ?? new ProjectFileLoaderRegistry(workspaceServices, _diagnosticReporter); _properties = ImmutableDictionary.Create(StringComparer.OrdinalIgnoreCase); @@ -53,7 +54,7 @@ internal MSBuildProjectLoader( /// An optional dictionary of additional MSBuild properties and values to use when loading projects. /// These are the same properties that are passed to msbuild via the /property:<n>=<v> command line argument. public MSBuildProjectLoader(Workspace workspace, ImmutableDictionary properties = null) - : this(workspace, diagnosticReporter: null, projectFileLoaderRegistry: null, properties) + : this(workspace.Services, new DiagnosticReporter(workspace), projectFileLoaderRegistry: null, properties) { } @@ -182,7 +183,7 @@ public async Task LoadSolutionInfoAsync( var buildManager = new ProjectBuildManager(_properties); var worker = new Worker( - _workspace, + _workspaceServices, _diagnosticReporter, _pathResolver, _projectFileLoaderRegistry, @@ -238,7 +239,7 @@ public async Task> LoadProjectInfoAsync( var buildManager = new ProjectBuildManager(_properties); var worker = new Worker( - _workspace, + _workspaceServices, _diagnosticReporter, _pathResolver, _projectFileLoaderRegistry, diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs index 30fe08e4b1814..89d9300b8a0fe 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs @@ -30,17 +30,16 @@ public sealed class MSBuildWorkspace : Workspace private readonly MSBuildProjectLoader _loader; private readonly ProjectFileLoaderRegistry _projectFileLoaderRegistry; - - private ImmutableList _diagnostics = ImmutableList.Empty; + private readonly DiagnosticReporter _reporter; private MSBuildWorkspace( HostServices hostServices, ImmutableDictionary properties) : base(hostServices, WorkspaceKind.MSBuild) { - var diagnosticReporter = new DiagnosticReporter(this); - _projectFileLoaderRegistry = new ProjectFileLoaderRegistry(this, diagnosticReporter); - _loader = new MSBuildProjectLoader(this, diagnosticReporter, _projectFileLoaderRegistry, properties); + _reporter = new DiagnosticReporter(this); + _projectFileLoaderRegistry = new ProjectFileLoaderRegistry(Services, _reporter); + _loader = new MSBuildProjectLoader(Services, _reporter, _projectFileLoaderRegistry, properties); } /// @@ -100,13 +99,7 @@ public static MSBuildWorkspace Create(IDictionary properties, Ho /// /// Diagnostics logged while opening solutions, projects and documents. /// - public ImmutableList Diagnostics => _diagnostics; - - protected internal override void OnWorkspaceFailed(WorkspaceDiagnostic diagnostic) - { - ImmutableInterlocked.Update(ref _diagnostics, d => d.Add(diagnostic)); - base.OnWorkspaceFailed(diagnostic); - } + public ImmutableList Diagnostics => _reporter.Diagnostics; /// /// Determines if metadata from existing output assemblies is loaded instead of opening referenced projects. @@ -291,7 +284,7 @@ protected override void ApplyProjectChanges(ProjectChanges projectChanges) } catch (IOException exception) { - this.OnWorkspaceFailed(new ProjectDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, projectChanges.ProjectId)); + _reporter.Report(new ProjectDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, projectChanges.ProjectId)); } } } @@ -308,7 +301,7 @@ protected override void ApplyProjectChanges(ProjectChanges projectChanges) } catch (IOException exception) { - this.OnWorkspaceFailed(new ProjectDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, projectChanges.ProjectId)); + _reporter.Report(new ProjectDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, projectChanges.ProjectId)); } } } @@ -403,7 +396,7 @@ private void SaveDocumentText(DocumentId id, string fullPath, SourceText newText } catch (IOException exception) { - this.OnWorkspaceFailed(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, id)); + _reporter.Report(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, id)); } } @@ -431,15 +424,15 @@ private void DeleteDocumentFile(DocumentId documentId, string fullPath) } catch (IOException exception) { - this.OnWorkspaceFailed(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, documentId)); + _reporter.Report(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, documentId)); } catch (NotSupportedException exception) { - this.OnWorkspaceFailed(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, documentId)); + _reporter.Report(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, documentId)); } catch (UnauthorizedAccessException exception) { - this.OnWorkspaceFailed(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, documentId)); + _reporter.Report(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, documentId)); } } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoader.cs b/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoader.cs index d3fd39f01de8d..c07606ed14d2f 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoader.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoader.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.MSBuild.Build; using Microsoft.CodeAnalysis.MSBuild.Logging; using Roslyn.Utilities; @@ -31,9 +32,9 @@ public async Task LoadProjectFileAsync(string path, ProjectBuildMa return this.CreateProjectFile(project, buildManager, log); } - public static IProjectFileLoader GetLoaderForProjectFileExtension(Workspace workspace, string extension) + public static IProjectFileLoader GetLoaderForProjectFileExtension(HostWorkspaceServices workspaceServices, string extension) { - return workspace.Services.FindLanguageServices( + return workspaceServices.FindLanguageServices( d => d.GetEnumerableMetadata("ProjectFileExtension").Any(e => string.Equals(e, extension, StringComparison.OrdinalIgnoreCase))) .FirstOrDefault(); } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoaderRegistry.cs b/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoaderRegistry.cs index bd4d601116e06..dbf5a2d409963 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoaderRegistry.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoaderRegistry.cs @@ -10,14 +10,14 @@ namespace Microsoft.CodeAnalysis.MSBuild { internal class ProjectFileLoaderRegistry { - private readonly Workspace _workspace; + private readonly HostWorkspaceServices _workspaceServices; private readonly DiagnosticReporter _diagnosticReporter; private readonly Dictionary _extensionToLanguageMap; private readonly NonReentrantLock _dataGuard; - public ProjectFileLoaderRegistry(Workspace workspace, DiagnosticReporter diagnosticReporter) + public ProjectFileLoaderRegistry(HostWorkspaceServices workspaceServices, DiagnosticReporter diagnosticReporter) { - _workspace = workspace; + _workspaceServices = workspaceServices; _diagnosticReporter = diagnosticReporter; _extensionToLanguageMap = new Dictionary(StringComparer.OrdinalIgnoreCase); _dataGuard = new NonReentrantLock(); @@ -51,9 +51,9 @@ public bool TryGetLoaderFromProjectPath(string projectFilePath, DiagnosticReport if (_extensionToLanguageMap.TryGetValue(extension, out var language)) { - if (_workspace.Services.SupportedLanguages.Contains(language)) + if (_workspaceServices.SupportedLanguages.Contains(language)) { - loader = _workspace.Services.GetLanguageServices(language).GetService(); + loader = _workspaceServices.GetLanguageServices(language).GetService(); } else { @@ -64,7 +64,7 @@ public bool TryGetLoaderFromProjectPath(string projectFilePath, DiagnosticReport } else { - loader = ProjectFileLoader.GetLoaderForProjectFileExtension(_workspace, extension); + loader = ProjectFileLoader.GetLoaderForProjectFileExtension(_workspaceServices, extension); if (loader == null) { @@ -79,7 +79,7 @@ public bool TryGetLoaderFromProjectPath(string projectFilePath, DiagnosticReport language = loader.Language; // check for command line parser existing... if not then error. - var commandLineParser = _workspace.Services + var commandLineParser = _workspaceServices .GetLanguageServices(language) .GetService(); diff --git a/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs b/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs index 35453b332c932..8198fb902d3f8 100644 --- a/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs +++ b/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs @@ -946,12 +946,6 @@ public async Task TestOpenSolution_WithTemporaryLockedFile_SucceedsWithoutFailur using (var ws = CreateMSBuildWorkspace()) { - var failed = false; - ws.WorkspaceFailed += (s, args) => - { - failed |= args.Diagnostic is DocumentDiagnostic; - }; - // open source file so it cannot be read by workspace; var sourceFile = GetSolutionFileName(@"CSharpProject\CSharpClass.cs"); var file = File.Open(sourceFile, FileMode.Open, FileAccess.Write, FileShare.None); @@ -976,7 +970,7 @@ public async Task TestOpenSolution_WithTemporaryLockedFile_SucceedsWithoutFailur file.Close(); } - Assert.False(failed); + Assert.Empty(ws.Diagnostics); } } @@ -990,12 +984,6 @@ public async Task TestOpenSolution_WithLockedFile_FailsWithFailureEvent() using (var workspace = CreateMSBuildWorkspace()) { - var failed = false; - workspace.WorkspaceFailed += (s, args) => - { - failed |= args.Diagnostic is DocumentDiagnostic; - }; - // open source file so it cannot be read by workspace; var sourceFile = GetSolutionFileName(@"CSharpProject\CSharpClass.cs"); var file = File.Open(sourceFile, FileMode.Open, FileAccess.Write, FileShare.None); @@ -1011,11 +999,10 @@ public async Task TestOpenSolution_WithLockedFile_FailsWithFailureEvent() file.Close(); } - Assert.True(failed); + Assert.Equal(WorkspaceDiagnosticKind.Failure, workspace.Diagnostics.Single().Kind); } } - [ConditionalFact(typeof(VisualStudioMSBuildInstalled)), Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)] public async Task TestOpenSolution_WithInvalidProjectPath_SkipTrue_SucceedsWithFailureEvent() { @@ -1028,15 +1015,8 @@ public async Task TestOpenSolution_WithInvalidProjectPath_SkipTrue_SucceedsWithF using (var workspace = CreateMSBuildWorkspace()) { - var diagnostics = new List(); - workspace.WorkspaceFailed += (s, args) => - { - diagnostics.Add(args.Diagnostic); - }; - var solution = await workspace.OpenSolutionAsync(solutionFilePath); - - Assert.Single(diagnostics); + Assert.Single(workspace.Diagnostics); } } @@ -1050,15 +1030,8 @@ public async Task HandleSolutionProjectTypeSolutionFolder() using (var workspace = CreateMSBuildWorkspace()) { - var diagnostics = new List(); - workspace.WorkspaceFailed += (s, args) => - { - diagnostics.Add(args.Diagnostic); - }; - var solution = await workspace.OpenSolutionAsync(solutionFilePath); - - Assert.Empty(diagnostics); + Assert.Empty(workspace.Diagnostics); } } @@ -1093,15 +1066,9 @@ public async Task TestOpenSolution_WithNonExistentProject_SkipTrue_SucceedsWithF using (var workspace = CreateMSBuildWorkspace()) { - var diagnostics = new List(); - workspace.WorkspaceFailed += (s, args) => - { - diagnostics.Add(args.Diagnostic); - }; - var solution = await workspace.OpenSolutionAsync(solutionFilePath); - Assert.Single(diagnostics); + Assert.Single(workspace.Diagnostics); } } @@ -1168,15 +1135,9 @@ public async Task TestOpenSolution_WithUnrecognizedProjectTypeGuidAndUnrecognize using (var workspace = CreateMSBuildWorkspace()) { - var diagnostics = new List(); - workspace.WorkspaceFailed += (s, args) => - { - diagnostics.Add(args.Diagnostic); - }; - var solution = await workspace.OpenSolutionAsync(solutionFilePath); - Assert.Single(diagnostics); + Assert.Single(workspace.Diagnostics); Assert.Empty(solution.ProjectIds); } } @@ -1246,19 +1207,11 @@ public async Task TestOpenSolution_WithMissingLanguageLibraries_WithSkipTrue_Suc { workspace.SkipUnrecognizedProjects = true; - var diagnostics = new List(); - workspace.WorkspaceFailed += delegate (object sender, WorkspaceDiagnosticEventArgs e) - { - diagnostics.Add(e.Diagnostic); - }; - var solution = await workspace.OpenSolutionAsync(solutionFilePath); - Assert.Single(diagnostics); - var projFileName = GetSolutionFileName(@"CSharpProject\CSharpProject.csproj"); var expected = string.Format(WorkspacesResources.Cannot_open_project_0_because_the_file_extension_1_is_not_associated_with_a_language, projFileName, ".csproj"); - Assert.Equal(expected, diagnostics[0].Message); + Assert.Equal(expected, workspace.Diagnostics.Single().Message); } } @@ -1323,17 +1276,13 @@ public async Task TestOpenProject_WithInvalidProjectReference_SkipTrue_SucceedsW using (var workspace = CreateMSBuildWorkspace()) { - var diagnostics = new List(); - workspace.WorkspaceFailed += (s, args) => - { - diagnostics.Add(args.Diagnostic); - }; - var project = await workspace.OpenProjectAsync(projectFilePath); Assert.Single(project.Solution.ProjectIds); // didn't really open referenced project due to invalid file path. Assert.Empty(project.ProjectReferences); // no resolved project references Assert.Single(project.AllProjectReferences); // dangling project reference + + Assert.Empty(workspace.Diagnostics); } } @@ -1363,17 +1312,13 @@ public async Task TestOpenProject_WithNonExistentProjectReference_SkipTrue_Succe using (var workspace = CreateMSBuildWorkspace()) { - var diagnostics = new List(); - workspace.WorkspaceFailed += (s, args) => - { - diagnostics.Add(args.Diagnostic); - }; - var project = await workspace.OpenProjectAsync(projectFilePath); Assert.Single(project.Solution.ProjectIds); // didn't really open referenced project due to invalid file path. Assert.Empty(project.ProjectReferences); // no resolved project references Assert.Single(project.AllProjectReferences); // dangling project reference + + Assert.Empty(workspace.Diagnostics); } } @@ -1404,17 +1349,13 @@ public async Task TestOpenProject_WithUnrecognizedProjectReferenceFileExtension_ using (var workspace = CreateMSBuildWorkspace()) { - var diagnostics = new List(); - workspace.WorkspaceFailed += (s, args) => - { - diagnostics.Add(args.Diagnostic); - }; - var project = await workspace.OpenProjectAsync(projectFilePath); Assert.Single(project.Solution.ProjectIds); // didn't really open referenced project due to unrecognized extension. Assert.Empty(project.ProjectReferences); // no resolved project references Assert.Single(project.AllProjectReferences); // dangling project reference + + Assert.Empty(workspace.Diagnostics); } } From 57e94f9fe10f4186f86debaf74b7c3593d07abb1 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Thu, 28 Nov 2019 11:39:43 -0800 Subject: [PATCH 5/8] Fixes --- src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs | 6 +++++- src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs | 6 ++++++ src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs | 6 +++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs b/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs index 59dad12804055..9287aa0a71363 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs @@ -41,9 +41,13 @@ public void Report(DiagnosticReportingMode mode, string message, Func d.Add(diagnostic)); + } + + public void Report(WorkspaceDiagnostic diagnostic) + { _workspace.OnWorkspaceFailed(diagnostic); } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs index 89d9300b8a0fe..bd7b244c3cdc3 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs @@ -101,6 +101,12 @@ public static MSBuildWorkspace Create(IDictionary properties, Ho /// public ImmutableList Diagnostics => _reporter.Diagnostics; + protected internal override void OnWorkspaceFailed(WorkspaceDiagnostic diagnostic) + { + _reporter.AddDiagnostic(diagnostic); + base.OnWorkspaceFailed(diagnostic); + } + /// /// Determines if metadata from existing output assemblies is loaded instead of opening referenced projects. /// If the referenced project is already opened, the metadata will not be loaded. diff --git a/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs b/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs index 8198fb902d3f8..6774689849559 100644 --- a/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs +++ b/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs @@ -1282,7 +1282,7 @@ public async Task TestOpenProject_WithInvalidProjectReference_SkipTrue_SucceedsW Assert.Empty(project.ProjectReferences); // no resolved project references Assert.Single(project.AllProjectReferences); // dangling project reference - Assert.Empty(workspace.Diagnostics); + Assert.NotEmpty(workspace.Diagnostics); } } @@ -1318,7 +1318,7 @@ public async Task TestOpenProject_WithNonExistentProjectReference_SkipTrue_Succe Assert.Empty(project.ProjectReferences); // no resolved project references Assert.Single(project.AllProjectReferences); // dangling project reference - Assert.Empty(workspace.Diagnostics); + Assert.NotEmpty(workspace.Diagnostics); } } @@ -1355,7 +1355,7 @@ public async Task TestOpenProject_WithUnrecognizedProjectReferenceFileExtension_ Assert.Empty(project.ProjectReferences); // no resolved project references Assert.Single(project.AllProjectReferences); // dangling project reference - Assert.Empty(workspace.Diagnostics); + Assert.NotEmpty(workspace.Diagnostics); } } From d3c91f54b180d0d4855edf3e57990f7bff3c6b45 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Wed, 4 Dec 2019 17:38:42 -0800 Subject: [PATCH 6/8] Feedback --- .../Test2/Diagnostics/DiagnosticServiceTests.vb | 4 ++-- .../Core/Portable/Diagnostics/AnalyzerHelper.cs | 11 ++++++++--- .../Diagnostics/DefaultDiagnosticAnalyzerService.cs | 2 +- .../DiagnosticIncrementalAnalyzer.Executor.cs | 2 +- .../Core/MSBuild/MSBuild/DiagnosticReporter.cs | 2 +- .../Core/Portable/Workspace/FileTextLoader.cs | 2 +- .../Core/Portable/Workspace/Solution/DocumentState.cs | 2 +- .../Workspace/WorkspaceDiagnosticDescriptors.cs | 2 +- .../Core/Portable/WorkspacesResources.Designer.cs | 6 +++--- src/Workspaces/Core/Portable/WorkspacesResources.resx | 2 +- .../Core/Portable/xlf/WorkspacesResources.cs.xlf | 2 +- .../Core/Portable/xlf/WorkspacesResources.de.xlf | 2 +- .../Core/Portable/xlf/WorkspacesResources.es.xlf | 2 +- .../Core/Portable/xlf/WorkspacesResources.fr.xlf | 2 +- .../Core/Portable/xlf/WorkspacesResources.it.xlf | 2 +- .../Core/Portable/xlf/WorkspacesResources.ja.xlf | 2 +- .../Core/Portable/xlf/WorkspacesResources.ko.xlf | 2 +- .../Core/Portable/xlf/WorkspacesResources.pl.xlf | 2 +- .../Core/Portable/xlf/WorkspacesResources.pt-BR.xlf | 2 +- .../Core/Portable/xlf/WorkspacesResources.ru.xlf | 2 +- .../Core/Portable/xlf/WorkspacesResources.tr.xlf | 2 +- .../Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf | 2 +- .../Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf | 2 +- .../CoreTest/SolutionTests/SolutionTests.cs | 7 +++++++ 24 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index b5268ef52a97c..38928400a2311 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -569,7 +569,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim diagnostics = Await diagnosticService.GetDiagnosticsForSpanAsync(document, span).ConfigureAwait(False) Assert.Equal(1, diagnostics.Count()) Assert.True(diagnostics(0).Id = "IDE1100") - Assert.Equal(String.Format(WorkspacesResources.Error_reading_source_file_content, "Test.cs", "Bad data!"), diagnostics(0).Message) + Assert.Equal(String.Format(WorkspacesResources.Error_reading_content_of_source_file_0_1, "Test.cs", "Bad data!"), diagnostics(0).Message) ' analyzer should not be executed on a file that can't be loaded diagnostics = exceptionDiagnosticsSource.GetTestAccessor().GetReportedDiagnostics(analyzer) @@ -907,7 +907,7 @@ class AnonymousFunctions ' The analyzer is invoked but the compilation does not contain a syntax tree that failed to load. AssertEx.Equal( { - "IDE1100: " & String.Format(WorkspacesResources.Error_reading_source_file_content, "Test.cs", "Bad data!"), + "IDE1100: " & String.Format(WorkspacesResources.Error_reading_content_of_source_file_0_1, "Test.cs", "Bad data!"), "StatefulCompilationAnalyzerDiagnostic: Compilation NamedType Count: 0" }, projectDiagnostics.Select(Function(d) d.Id & ": " & d.GetMessage())) End Using diff --git a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs index 973d5168da339..34f1448ad44f3 100644 --- a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs +++ b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs @@ -312,13 +312,18 @@ public static async Task> ComputeDiagnosticsAsync( { var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); - if (analyzer == FileContentLoadAnalyzer.Instance || loadDiagnostic != null) + if (analyzer == FileContentLoadAnalyzer.Instance) { - return (analyzer == FileContentLoadAnalyzer.Instance && loadDiagnostic != null) ? - SpecializedCollections.SingletonEnumerable(DiagnosticData.Create(document, loadDiagnostic)) : + return loadDiagnostic != null ? + SpecializedCollections.SingletonEnumerable(DiagnosticData.Create(loadDiagnostic, document)) : SpecializedCollections.EmptyEnumerable(); } + if (loadDiagnostic != null) + { + return SpecializedCollections.EmptyEnumerable(); + } + if (analyzer is DocumentDiagnosticAnalyzer documentAnalyzer) { var diagnostics = await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( diff --git a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs index 91a46bd22c3f3..a609656f0ee74 100644 --- a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs @@ -149,7 +149,7 @@ private async Task> GetDiagnosticsAsync( var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); if (loadDiagnostic != null) { - return ImmutableArray.Create(DiagnosticData.Create(document, loadDiagnostic)); + return ImmutableArray.Create(DiagnosticData.Create(loadDiagnostic, document)); } // given service must be DiagnosticAnalyzerService diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index 467a1192d4523..711e18206f31e 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -361,7 +361,7 @@ private async Task>(); - lazyLoadDiagnostics.Add(document.Id, ImmutableArray.Create(DiagnosticData.Create(project.Solution.Workspace, loadDiagnostic, project.Id))); + lazyLoadDiagnostics.Add(document.Id, ImmutableArray.Create(DiagnosticData.Create(loadDiagnostic, document))); failedDocuments ??= PooledHashSet.GetInstance(); failedDocuments.Add(document); diff --git a/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs b/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs index 9287aa0a71363..25796661e3cff 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs @@ -43,7 +43,7 @@ public void Report(DiagnosticReportingMode mode, string message, Func d.Add(diagnostic)); + ImmutableInterlocked.Update(ref Diagnostics, (list, d) => list.Add(d), diagnostic); } public void Report(WorkspaceDiagnostic diagnostic) diff --git a/src/Workspaces/Core/Portable/Workspace/FileTextLoader.cs b/src/Workspaces/Core/Portable/Workspace/FileTextLoader.cs index d8b48c814e921..fe9ab2ed223ca 100644 --- a/src/Workspaces/Core/Portable/Workspace/FileTextLoader.cs +++ b/src/Workspaces/Core/Portable/Workspace/FileTextLoader.cs @@ -80,7 +80,7 @@ public FileTextLoader(string path, Encoding? defaultEncoding) DefaultEncoding = defaultEncoding; } - internal sealed override string? FilePath => Path; + internal sealed override string FilePath => Path; protected virtual SourceText CreateText(Stream stream, Workspace workspace) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index 08bb4bb327301..4eadbc97caf45 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -670,7 +670,7 @@ private static (ValueSource, TreeAndVersion) CreateRecoverableTe { if (TextAndVersionSource is TreeTextSource) { - return Task.FromResult((Diagnostic?)null); + return SpecializedTasks.Default(); } return base.GetLoadDiagnosticAsync(cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/WorkspaceDiagnosticDescriptors.cs b/src/Workspaces/Core/Portable/Workspace/WorkspaceDiagnosticDescriptors.cs index 6571da3eb53ff..ae44a411da837 100644 --- a/src/Workspaces/Core/Portable/Workspace/WorkspaceDiagnosticDescriptors.cs +++ b/src/Workspaces/Core/Portable/Workspace/WorkspaceDiagnosticDescriptors.cs @@ -13,7 +13,7 @@ static WorkspaceDiagnosticDescriptors() ErrorReadingFileContent = new DiagnosticDescriptor( id: ErrorReadingFileContentId, title: new LocalizableResourceString(nameof(WorkspacesResources.Workspace_error), WorkspacesResources.ResourceManager, typeof(WorkspacesResources)), - messageFormat: new LocalizableResourceString(nameof(WorkspacesResources.Error_reading_source_file_content), WorkspacesResources.ResourceManager, typeof(WorkspacesResources)), + messageFormat: new LocalizableResourceString(nameof(WorkspacesResources.Error_reading_content_of_source_file_0_1), WorkspacesResources.ResourceManager, typeof(WorkspacesResources)), category: WorkspacesResources.Workspace_error, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, diff --git a/src/Workspaces/Core/Portable/WorkspacesResources.Designer.cs b/src/Workspaces/Core/Portable/WorkspacesResources.Designer.cs index 29f9ccd8d60f7..ced0155d18fa0 100644 --- a/src/Workspaces/Core/Portable/WorkspacesResources.Designer.cs +++ b/src/Workspaces/Core/Portable/WorkspacesResources.Designer.cs @@ -784,9 +784,9 @@ internal static string Error { /// /// Looks up a localized string similar to Error reading content of source file '{0}' -- '{1}'.. /// - internal static string Error_reading_source_file_content { + internal static string Error_reading_content_of_source_file_0_1 { get { - return ResourceManager.GetString("Error_reading_source_file_content", resourceCulture); + return ResourceManager.GetString("Error_reading_content_of_source_file_0_1", resourceCulture); } } @@ -3810,7 +3810,7 @@ internal static string Warning_adding_imports_will_bring_an_extension_method_int } /// - /// Looks up a localized string similar to Workspace error.. + /// Looks up a localized string similar to Workspace error. /// internal static string Workspace_error { get { diff --git a/src/Workspaces/Core/Portable/WorkspacesResources.resx b/src/Workspaces/Core/Portable/WorkspacesResources.resx index ef79fc9254f58..79e4b1af5c6ea 100644 --- a/src/Workspaces/Core/Portable/WorkspacesResources.resx +++ b/src/Workspaces/Core/Portable/WorkspacesResources.resx @@ -171,7 +171,7 @@ Workspace error - + Error reading content of source file '{0}' -- '{1}'. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf index 416f5eeabcc9b..be72d4cb491a8 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf @@ -52,7 +52,7 @@ Document does not support syntax trees - + Error reading content of source file '{0}' -- '{1}'. Error reading content of source file '{0}' -- '{1}'. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf index e864596bf427e..8c34defb72b06 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf @@ -52,7 +52,7 @@ Document does not support syntax trees - + Error reading content of source file '{0}' -- '{1}'. Error reading content of source file '{0}' -- '{1}'. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf index effee74b12259..3cc9bfced9f05 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf @@ -52,7 +52,7 @@ Document does not support syntax trees - + Error reading content of source file '{0}' -- '{1}'. Error reading content of source file '{0}' -- '{1}'. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf index 2a0fe52c58e1a..677386f24b0e1 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf @@ -52,7 +52,7 @@ Document does not support syntax trees - + Error reading content of source file '{0}' -- '{1}'. Error reading content of source file '{0}' -- '{1}'. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf index b1b27653b4183..1260c6b6b9464 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf @@ -52,7 +52,7 @@ Document does not support syntax trees - + Error reading content of source file '{0}' -- '{1}'. Error reading content of source file '{0}' -- '{1}'. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf index 8fd7b5f3354e6..c0f45861d4f7e 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf @@ -52,7 +52,7 @@ Document does not support syntax trees - + Error reading content of source file '{0}' -- '{1}'. Error reading content of source file '{0}' -- '{1}'. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf index dfff156da133c..bbc5a12ce6e05 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf @@ -52,7 +52,7 @@ Document does not support syntax trees - + Error reading content of source file '{0}' -- '{1}'. Error reading content of source file '{0}' -- '{1}'. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf index 0edb692181ca8..d4a4ec62a720f 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf @@ -52,7 +52,7 @@ Document does not support syntax trees - + Error reading content of source file '{0}' -- '{1}'. Error reading content of source file '{0}' -- '{1}'. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf index b5f8885e5efe8..30fccff7fd3f9 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf @@ -52,7 +52,7 @@ Document does not support syntax trees - + Error reading content of source file '{0}' -- '{1}'. Error reading content of source file '{0}' -- '{1}'. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf index 50a4995e32aa6..d4f35beb00394 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf @@ -52,7 +52,7 @@ Document does not support syntax trees - + Error reading content of source file '{0}' -- '{1}'. Error reading content of source file '{0}' -- '{1}'. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf index eb2c92e6ac088..6dffdfd07611c 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf @@ -52,7 +52,7 @@ Document does not support syntax trees - + Error reading content of source file '{0}' -- '{1}'. Error reading content of source file '{0}' -- '{1}'. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf index 577562c52e283..174002e70e8d9 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf @@ -52,7 +52,7 @@ Document does not support syntax trees - + Error reading content of source file '{0}' -- '{1}'. Error reading content of source file '{0}' -- '{1}'. diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf index a230e0b4d71ef..a03926331975a 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf @@ -52,7 +52,7 @@ Document does not support syntax trees - + Error reading content of source file '{0}' -- '{1}'. Error reading content of source file '{0}' -- '{1}'. diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 9493d61f8662e..a17e4aeeefc20 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -1249,6 +1249,12 @@ public async Task TestDocumentFileAccessFailureMissingFile() var workspace = new AdhocWorkspace(); var solution = workspace.CurrentSolution; + WorkspaceDiagnostic diagnosticFromEvent = null; + solution.Workspace.WorkspaceFailed += (sender, args) => + { + diagnosticFromEvent = args.Diagnostic; + }; + var pid = ProjectId.CreateNewId(); var did = DocumentId.CreateNewId(pid); @@ -1262,6 +1268,7 @@ public async Task TestDocumentFileAccessFailureMissingFile() var diagnostic = await doc.State.GetLoadDiagnosticAsync(CancellationToken.None).ConfigureAwait(false); Assert.Equal(@"C:\doesnotexist.cs: (0,0)-(0,0)", diagnostic.Location.GetLineSpan().ToString()); + Assert.Equal(WorkspaceDiagnosticKind.Failure, diagnosticFromEvent.Kind); Assert.Equal("", text.ToString()); } From ede8613f467ca52d4bf64c6227fb4d929102f37f Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Thu, 5 Dec 2019 10:09:43 -0800 Subject: [PATCH 7/8] Fix test --- .../Test2/Diagnostics/DiagnosticServiceTests.vb | 8 +++++++- .../Diagnostics/TestDiagnosticAnalyzerDriver.cs | 12 ------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 38928400a2311..a2084d37d2031 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -907,9 +907,15 @@ class AnonymousFunctions ' The analyzer is invoked but the compilation does not contain a syntax tree that failed to load. AssertEx.Equal( { - "IDE1100: " & String.Format(WorkspacesResources.Error_reading_content_of_source_file_0_1, "Test.cs", "Bad data!"), "StatefulCompilationAnalyzerDiagnostic: Compilation NamedType Count: 0" }, projectDiagnostics.Select(Function(d) d.Id & ": " & d.GetMessage())) + + Dim documentDiagnostics = Await DiagnosticProviderTestUtilities.GetDocumentDiagnosticsAsync(workspaceAnalyzerOpt:=Nothing, document, TextSpan.FromBounds(0, 0)) + AssertEx.Equal( + { + "IDE1100: " & String.Format(WorkspacesResources.Error_reading_content_of_source_file_0_1, "Test.cs", "Bad data!") + }, documentDiagnostics.Select(Function(d) d.Id & ": " & d.GetMessage())) + End Using End Function diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs b/src/EditorFeatures/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs index e13c7f759423d..74fe4f283d668 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs @@ -106,18 +106,6 @@ public async Task> GetAllDiagnosticsAsync(Project projec return diagnostics; } - public async Task> GetAllDiagnosticsAsync(DiagnosticAnalyzer workspaceAnalyzerOpt, Solution solution) - { - var diagnostics = new List(); - foreach (var project in solution.Projects) - { - var projectDiagnostics = await GetAllDiagnosticsAsync(project); - diagnostics.AddRange(projectDiagnostics); - } - - return diagnostics; - } - public Task> GetDocumentDiagnosticsAsync(Document document, TextSpan span) { return GetDiagnosticsAsync(document.Project, document, span, getDocumentDiagnostics: true, getProjectDiagnostics: false); From 9272a95a59a97919d15261ea0adf767ff7bc6e7f Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Thu, 5 Dec 2019 15:57:50 -0800 Subject: [PATCH 8/8] Fix pooled hashset leak --- .../EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index 711e18206f31e..ace8625a6ff41 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -350,9 +350,9 @@ private async Task? failedDocuments)> GetDocumentLoadFailuresAsync(Project project, VersionStamp version, CancellationToken cancellationToken) + private async Task<(DiagnosticAnalysisResult loadDiagnostics, ImmutableHashSet? failedDocuments)> GetDocumentLoadFailuresAsync(Project project, VersionStamp version, CancellationToken cancellationToken) { - PooledHashSet? failedDocuments = null; + ImmutableHashSet.Builder? failedDocuments = null; ImmutableDictionary>.Builder? lazyLoadDiagnostics = null; foreach (var document in project.Documents) @@ -363,7 +363,7 @@ private async Task>(); lazyLoadDiagnostics.Add(document.Id, ImmutableArray.Create(DiagnosticData.Create(loadDiagnostic, document))); - failedDocuments ??= PooledHashSet.GetInstance(); + failedDocuments ??= ImmutableHashSet.CreateBuilder(); failedDocuments.Add(document); } } @@ -377,7 +377,7 @@ private async Task.Empty, documentIds: null); - return (result, failedDocuments); + return (result, failedDocuments?.ToImmutable()); } private void UpdateAnalyzerTelemetryData(ImmutableDictionary telemetry)