diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index e6392ad029cfd..3f228631a5b1d 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -275,17 +275,13 @@ public void TestHostAnalyzerOrdering() var incrementalAnalyzer = (DiagnosticIncrementalAnalyzer)service.CreateIncrementalAnalyzer(workspace); var analyzers = incrementalAnalyzer.GetAnalyzersTestOnly(project).ToArray(); - AssertEx.Equal(new[] - { - typeof(FileContentLoadAnalyzer), - typeof(CSharpCompilerDiagnosticAnalyzer), - typeof(Analyzer), - typeof(Priority0Analyzer), - typeof(Priority1Analyzer), - typeof(Priority10Analyzer), - typeof(Priority15Analyzer), - typeof(Priority20Analyzer) - }, analyzers.Select(a => a.GetType())); + 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()); } [Fact] diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 5ef3633e32fa8..f2cb640bd7897 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -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. Imports System.Collections.Immutable -Imports System.IO Imports System.Reflection Imports System.Threading Imports System.Threading.Tasks @@ -28,26 +27,6 @@ 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 = @@ -536,47 +515,6 @@ 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_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) - Assert.Empty(diagnostics) - End Using - End Function - Public Sub TestOperationAnalyzers() Dim test = @@ -879,46 +817,6 @@ 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( - { - "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 - Public Sub TestMultiplePartialDefinitionsInAFile() Dim test = diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs b/src/EditorFeatures/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs index 74fe4f283d668..e13c7f759423d 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs @@ -106,6 +106,18 @@ 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); diff --git a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs index 34f1448ad44f3..74acf15a31366 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 == FileContentLoadAnalyzer.Instance; + => analyzer is DocumentDiagnosticAnalyzer || analyzer is ProjectDiagnosticAnalyzer; public static bool IsBuiltInAnalyzer(this DiagnosticAnalyzer analyzer) => analyzer is IBuiltInAnalyzer || analyzer.IsWorkspaceDiagnosticAnalyzer() || analyzer.IsCompilerAnalyzer(); @@ -306,24 +306,10 @@ public static async Task> ComputeDiagnosticsAsync( Document document, DiagnosticAnalyzer analyzer, AnalysisKind kind, - TextSpan? span, + TextSpan? spanOpt, DiagnosticLogAggregator? logAggregator, CancellationToken cancellationToken) { - var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); - - if (analyzer == FileContentLoadAnalyzer.Instance) - { - 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( @@ -387,7 +373,7 @@ public static async Task> ComputeDiagnosticsAsync( return SpecializedCollections.EmptyEnumerable(); } - diagnostics = await compilationWithAnalyzers.GetAnalyzerSemanticDiagnosticsAsync(model, span, singleAnalyzer, cancellationToken).ConfigureAwait(false); + diagnostics = await compilationWithAnalyzers.GetAnalyzerSemanticDiagnosticsAsync(model, spanOpt, singleAnalyzer, cancellationToken).ConfigureAwait(false); Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationWithAnalyzers.Compilation).Count()); return diagnostics.ConvertToLocalDiagnostics(document); @@ -399,7 +385,7 @@ public static async Task> ComputeDiagnosticsAsync( public static bool SupportAnalysisKind(this DiagnosticAnalyzerService service, DiagnosticAnalyzer analyzer, string language, AnalysisKind kind) { - // compiler diagnostic analyzer always supports all kinds: + // compiler diagnostic analyzer always support all kinds if (service.IsCompilerDiagnosticAnalyzer(language, analyzer)) { return true; @@ -409,7 +395,7 @@ public static bool SupportAnalysisKind(this DiagnosticAnalyzerService service, D { AnalysisKind.Syntax => analyzer.SupportsSyntaxDiagnosticAnalysis(), AnalysisKind.Semantic => analyzer.SupportsSemanticDiagnosticAnalysis(), - _ => throw ExceptionUtilities.UnexpectedValue(kind) + _ => Contract.FailWithReturn("shouldn't reach here"), }; } @@ -426,6 +412,7 @@ 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 deleted file mode 100644 index 9a42c1639aaf3..0000000000000 --- a/src/Features/Core/Portable/Diagnostics/Analyzers/FileContentLoadAnalyzer.cs +++ /dev/null @@ -1,27 +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.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 32fb4148ed1d7..2d13ee4d60a70 100644 --- a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs +++ b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs @@ -124,7 +124,5 @@ 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 a609656f0ee74..6f62480e19909 100644 --- a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs @@ -146,12 +146,6 @@ 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(loadDiagnostic, document)); - } - // given service must be DiagnosticAnalyzerService var diagnosticService = (DiagnosticAnalyzerService)_service._analyzerService; @@ -164,7 +158,7 @@ private async Task> GetDiagnosticsAsync( foreach (var analyzer in analyzers) { builder.AddRange(await diagnosticService.ComputeDiagnosticsAsync( - compilationWithAnalyzers, document, analyzer, kind, span: null, logAggregator: null, cancellationToken).ConfigureAwait(false)); + compilationWithAnalyzers, document, analyzer, kind, spanOpt: 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 4650a317e864e..890246c739483 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 diagnostics = await AnalyzerService.ComputeDiagnosticsAsync(compilation, document, stateSet.Analyzer, kind, span: null, DiagnosticLogAggregator, cancellationToken).ConfigureAwait(false); + var nullFilterSpan = (TextSpan?)null; + var diagnostics = await ComputeDiagnosticsAsync(compilation, document, stateSet.Analyzer, kind, nullFilterSpan, 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,6 +165,12 @@ 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. /// @@ -298,42 +304,34 @@ private async Task? failedDocuments)> GetDocumentLoadFailuresAsync(Project project, VersionStamp version, CancellationToken cancellationToken) + private Task> ComputeProjectDiagnosticAnalyzerDiagnosticsAsync( + Project project, + ProjectDiagnosticAnalyzer analyzer, + Compilation? compilation, + CancellationToken cancellationToken) { - ImmutableHashSet.Builder? failedDocuments = null; - ImmutableDictionary>.Builder? lazyLoadDiagnostics = null; - - 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(loadDiagnostic, document))); - - failedDocuments ??= ImmutableHashSet.CreateBuilder(); - 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 AnalyzerService.ComputeProjectDiagnosticAnalyzerDiagnosticsAsync(project, analyzer, compilation, DiagnosticLogAggregator, cancellationToken); + } - return (result, failedDocuments?.ToImmutable()); + private Task> ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( + Document document, + DocumentDiagnosticAnalyzer analyzer, + AnalysisKind kind, + Compilation? compilation, + CancellationToken cancellationToken) + { + return AnalyzerService.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, analyzer, kind, compilation, DiagnosticLogAggregator, cancellationToken); } 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 ef905dae4718f..e1c763b8572d6 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 378dd812a04e4..2157380c28695 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, includeFileContentLoadAnalyzer: true); + var analyzerMap = CreateStateSetMap(analyzerInfoCache, language, analyzersPerReference.Values); VerifyUniqueStateNames(analyzerMap.Values); return new HostAnalyzerStateSets(analyzerInfoCache, language, analyzerMap); @@ -34,7 +34,6 @@ static HostAnalyzerStateSets CreateLanguageSpecificAnalyzerMap(string language, private sealed class HostAnalyzerStateSets { - private const int FileContentLoadAnalyzerPriority = -3; private const int BuiltInCompilerPriority = -2; private const int RegularDiagnosticAnalyzerPriority = -1; @@ -70,13 +69,16 @@ private int GetPriority(StateSet state) return BuiltInCompilerPriority; } - return state.Analyzer switch + switch (state.Analyzer) { - FileContentLoadAnalyzer _ => FileContentLoadAnalyzerPriority, - DocumentDiagnosticAnalyzer analyzer => Math.Max(0, analyzer.Priority), - ProjectDiagnosticAnalyzer analyzer => Math.Max(0, analyzer.Priority), - _ => RegularDiagnosticAnalyzerPriority, - }; + 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; + } } } } 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 4afe940afff36..2836da43ee5c5 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, includeFileContentLoadAnalyzer: false); + return CreateStateSetMap(_analyzerInfoCache, project.Language, analyzersPerReference.Values); } 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, includeFileContentLoadAnalyzer: false); + var newMap = CreateStateSetMap(_analyzerInfoCache, project.Language, newAnalyzersPerReference.Values); 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 452e062be830e..80c8b729dd9d7 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -261,26 +261,15 @@ private void RaiseProjectAnalyzerReferenceChanged(ProjectAnalyzerReferenceChange } private static ImmutableDictionary CreateStateSetMap( - DiagnosticAnalyzerInfoCache analyzerInfoCache, - string language, - IEnumerable> analyzerCollection, - bool includeFileContentLoadAnalyzer) + DiagnosticAnalyzerInfoCache analyzerInfoCache, string language, IEnumerable> analyzerCollection) { 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 a3afe0fdca340..a4d613ab80946 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.AnalyzerService.ComputeDiagnosticsAsync(_compilation, _document, analyzer, AnalysisKind.Syntax, _range, _owner.DiagnosticLogAggregator, cancellationToken); + return _owner.ComputeDiagnosticsAsync(_compilation, _document, analyzer, AnalysisKind.Syntax, _range, 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.AnalyzerService.ComputeDiagnosticsAsync(_compilation, _document, analyzer, AnalysisKind.Semantic, analysisSpan, _owner.DiagnosticLogAggregator, cancellationToken); + return _owner.ComputeDiagnosticsAsync(_compilation, _document, analyzer, AnalysisKind.Semantic, analysisSpan, 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 ecd872a3f827d..18eda63f01a37 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -304,9 +304,8 @@ private IEnumerable GetStateSetsForFullSolutionAnalysis(IEnumerable GetDiagnosticDescriptorsCore(Diagno public bool IsAnalyzerSuppressed(DiagnosticAnalyzer analyzer, Project project) { var options = project.CompilationOptions; - if (options == null || analyzer == FileContentLoadAnalyzer.Instance || IsCompilerDiagnosticAnalyzer(project.Language, analyzer)) + if (options == null || 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 33595011855bb..f6cd0b8684c46 100644 --- a/src/Features/Core/Portable/Shared/Extensions/DiagnosticAnalyzerExtensions.cs +++ b/src/Features/Core/Portable/Shared/Extensions/DiagnosticAnalyzerExtensions.cs @@ -7,16 +7,33 @@ namespace Microsoft.CodeAnalysis.Shared.Extensions internal static class DiagnosticAnalyzerExtensions { public static DiagnosticAnalyzerCategory GetDiagnosticAnalyzerCategory(this DiagnosticAnalyzer analyzer) - => analyzer switch + { + var category = DiagnosticAnalyzerCategory.None; + + if (analyzer is DocumentDiagnosticAnalyzer) + { + category |= DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis | DiagnosticAnalyzerCategory.SemanticDocumentAnalysis; + } + else if (analyzer is ProjectDiagnosticAnalyzer) { - 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 - }; + 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; + } public static bool SupportsSyntaxDiagnosticAnalysis(this DiagnosticAnalyzer analyzer) { diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/WorkspaceFailureOutputPane.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/WorkspaceFailureOutputPane.cs new file mode 100644 index 0000000000000..ffc4ff581476a --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/WorkspaceFailureOutputPane.cs @@ -0,0 +1,114 @@ +// 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 5eb4b977af814..157ebdaeb2822 100644 --- a/src/VisualStudio/Core/Def/RoslynPackage.cs +++ b/src/VisualStudio/Core/Def/RoslynPackage.cs @@ -42,6 +42,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Setup internal class RoslynPackage : AbstractPackage { private VisualStudioWorkspace _workspace; + private WorkspaceFailureOutputPane _outputPane; private IComponentModel _componentModel; private RuleSetEventHandler _ruleSetEventHandler; private IDisposable _solutionEventMonitor; @@ -75,6 +76,9 @@ 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 diff --git a/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs b/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs index 25796661e3cff..8f9659413b4d1 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/DiagnosticReporter.cs @@ -1,20 +1,17 @@ // 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 sealed class DiagnosticReporter + internal 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) @@ -41,11 +38,6 @@ public void Report(DiagnosticReportingMode mode, string message, Func list.Add(d), diagnostic); - } - public void Report(WorkspaceDiagnostic 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 42340eea78638..f7facfc2c582e 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 HostWorkspaceServices _workspaceServices; + private readonly Workspace _workspace; 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( - HostWorkspaceServices services, + Workspace workspace, DiagnosticReporter diagnosticReporter, PathResolver pathResolver, ProjectFileLoaderRegistry projectFileLoaderRegistry, @@ -84,7 +84,7 @@ public Worker( DiagnosticReportingOptions discoveredProjectOptions, bool preferMetadataForReferencesOfDiscoveredProjects) { - _workspaceServices = services; + _workspace = workspace; _diagnosticReporter = diagnosticReporter; _pathResolver = pathResolver; _projectFileLoaderRegistry = projectFileLoaderRegistry; @@ -494,13 +494,13 @@ private void CheckForDuplicateDocuments(ImmutableArray documents, private TLanguageService GetLanguageService(string languageName) where TLanguageService : ILanguageService - => _workspaceServices + => _workspace.Services .GetLanguageServices(languageName) .GetService(); private TWorkspaceService GetWorkspaceService() where TWorkspaceService : IWorkspaceService - => _workspaceServices + => _workspace.Services .GetService(); } } diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs index 822a01b53c967..e263db71796c1 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildProjectLoader.cs @@ -5,7 +5,6 @@ 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; @@ -18,7 +17,7 @@ namespace Microsoft.CodeAnalysis.MSBuild public partial class MSBuildProjectLoader { // the workspace that the projects and solutions are intended to be loaded into. - private readonly HostWorkspaceServices _workspaceServices; + private readonly Workspace _workspace; private readonly DiagnosticReporter _diagnosticReporter; private readonly PathResolver _pathResolver; @@ -29,15 +28,15 @@ public partial class MSBuildProjectLoader private ImmutableDictionary _properties; internal MSBuildProjectLoader( - HostWorkspaceServices workspaceServices, + Workspace workspace, DiagnosticReporter diagnosticReporter, ProjectFileLoaderRegistry projectFileLoaderRegistry, ImmutableDictionary properties) { - _workspaceServices = workspaceServices; - _diagnosticReporter = diagnosticReporter; + _workspace = workspace; + _diagnosticReporter = diagnosticReporter ?? new DiagnosticReporter(workspace); _pathResolver = new PathResolver(_diagnosticReporter); - _projectFileLoaderRegistry = projectFileLoaderRegistry ?? new ProjectFileLoaderRegistry(workspaceServices, _diagnosticReporter); + _projectFileLoaderRegistry = projectFileLoaderRegistry ?? new ProjectFileLoaderRegistry(workspace, _diagnosticReporter); _properties = ImmutableDictionary.Create(StringComparer.OrdinalIgnoreCase); @@ -54,7 +53,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.Services, new DiagnosticReporter(workspace), projectFileLoaderRegistry: null, properties) + : this(workspace, diagnosticReporter: null, projectFileLoaderRegistry: null, properties) { } @@ -183,7 +182,7 @@ public async Task LoadSolutionInfoAsync( var buildManager = new ProjectBuildManager(_properties); var worker = new Worker( - _workspaceServices, + _workspace, _diagnosticReporter, _pathResolver, _projectFileLoaderRegistry, @@ -239,7 +238,7 @@ public async Task> LoadProjectInfoAsync( var buildManager = new ProjectBuildManager(_properties); var worker = new Worker( - _workspaceServices, + _workspace, _diagnosticReporter, _pathResolver, _projectFileLoaderRegistry, diff --git a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs index bd7b244c3cdc3..30fe08e4b1814 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs @@ -30,16 +30,17 @@ public sealed class MSBuildWorkspace : Workspace private readonly MSBuildProjectLoader _loader; private readonly ProjectFileLoaderRegistry _projectFileLoaderRegistry; - private readonly DiagnosticReporter _reporter; + + private ImmutableList _diagnostics = ImmutableList.Empty; private MSBuildWorkspace( HostServices hostServices, ImmutableDictionary properties) : base(hostServices, WorkspaceKind.MSBuild) { - _reporter = new DiagnosticReporter(this); - _projectFileLoaderRegistry = new ProjectFileLoaderRegistry(Services, _reporter); - _loader = new MSBuildProjectLoader(Services, _reporter, _projectFileLoaderRegistry, properties); + var diagnosticReporter = new DiagnosticReporter(this); + _projectFileLoaderRegistry = new ProjectFileLoaderRegistry(this, diagnosticReporter); + _loader = new MSBuildProjectLoader(this, diagnosticReporter, _projectFileLoaderRegistry, properties); } /// @@ -99,11 +100,11 @@ public static MSBuildWorkspace Create(IDictionary properties, Ho /// /// Diagnostics logged while opening solutions, projects and documents. /// - public ImmutableList Diagnostics => _reporter.Diagnostics; + public ImmutableList Diagnostics => _diagnostics; protected internal override void OnWorkspaceFailed(WorkspaceDiagnostic diagnostic) { - _reporter.AddDiagnostic(diagnostic); + ImmutableInterlocked.Update(ref _diagnostics, d => d.Add(diagnostic)); base.OnWorkspaceFailed(diagnostic); } @@ -290,7 +291,7 @@ protected override void ApplyProjectChanges(ProjectChanges projectChanges) } catch (IOException exception) { - _reporter.Report(new ProjectDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, projectChanges.ProjectId)); + this.OnWorkspaceFailed(new ProjectDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, projectChanges.ProjectId)); } } } @@ -307,7 +308,7 @@ protected override void ApplyProjectChanges(ProjectChanges projectChanges) } catch (IOException exception) { - _reporter.Report(new ProjectDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, projectChanges.ProjectId)); + this.OnWorkspaceFailed(new ProjectDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, projectChanges.ProjectId)); } } } @@ -402,7 +403,7 @@ private void SaveDocumentText(DocumentId id, string fullPath, SourceText newText } catch (IOException exception) { - _reporter.Report(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, id)); + this.OnWorkspaceFailed(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, id)); } } @@ -430,15 +431,15 @@ private void DeleteDocumentFile(DocumentId documentId, string fullPath) } catch (IOException exception) { - _reporter.Report(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, documentId)); + this.OnWorkspaceFailed(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, documentId)); } catch (NotSupportedException exception) { - _reporter.Report(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, documentId)); + this.OnWorkspaceFailed(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, documentId)); } catch (UnauthorizedAccessException exception) { - _reporter.Report(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, documentId)); + this.OnWorkspaceFailed(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 c07606ed14d2f..d3fd39f01de8d 100644 --- a/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoader.cs +++ b/src/Workspaces/Core/MSBuild/MSBuild/ProjectFile/ProjectFileLoader.cs @@ -5,7 +5,6 @@ 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; @@ -32,9 +31,9 @@ public async Task LoadProjectFileAsync(string path, ProjectBuildMa return this.CreateProjectFile(project, buildManager, log); } - public static IProjectFileLoader GetLoaderForProjectFileExtension(HostWorkspaceServices workspaceServices, string extension) + public static IProjectFileLoader GetLoaderForProjectFileExtension(Workspace workspace, string extension) { - return workspaceServices.FindLanguageServices( + return workspace.Services.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 dbf5a2d409963..bd4d601116e06 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 HostWorkspaceServices _workspaceServices; + private readonly Workspace _workspace; private readonly DiagnosticReporter _diagnosticReporter; private readonly Dictionary _extensionToLanguageMap; private readonly NonReentrantLock _dataGuard; - public ProjectFileLoaderRegistry(HostWorkspaceServices workspaceServices, DiagnosticReporter diagnosticReporter) + public ProjectFileLoaderRegistry(Workspace workspace, DiagnosticReporter diagnosticReporter) { - _workspaceServices = workspaceServices; + _workspace = workspace; _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 (_workspaceServices.SupportedLanguages.Contains(language)) + if (_workspace.Services.SupportedLanguages.Contains(language)) { - loader = _workspaceServices.GetLanguageServices(language).GetService(); + loader = _workspace.Services.GetLanguageServices(language).GetService(); } else { @@ -64,7 +64,7 @@ public bool TryGetLoaderFromProjectPath(string projectFilePath, DiagnosticReport } else { - loader = ProjectFileLoader.GetLoaderForProjectFileExtension(_workspaceServices, extension); + loader = ProjectFileLoader.GetLoaderForProjectFileExtension(_workspace, 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 = _workspaceServices + var commandLineParser = _workspace.Services .GetLanguageServices(language) .GetService(); diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs index f2c06a2b755cb..f94678acfaef9 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 Create( + public static DiagnosticAnalysisResult CreateFromSerialization( Project project, VersionStamp version, ImmutableDictionary> syntaxLocalMap, @@ -163,7 +163,7 @@ public static DiagnosticAnalysisResult Create( public static DiagnosticAnalysisResult CreateFromBuilder(DiagnosticAnalysisResultBuilder builder) { - return Create( + return CreateFromSerialization( 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 7421b72a8f0ec..55b57ad79c4ae 100644 --- a/src/Workspaces/Core/Portable/Utilities/ValuesSources/ConstantValueSource.cs +++ b/src/Workspaces/Core/Portable/Utilities/ValuesSources/ConstantValueSource.cs @@ -1,8 +1,5 @@ // 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; @@ -14,7 +11,7 @@ namespace Roslyn.Utilities internal sealed class ConstantValueSource : ValueSource { private readonly T _value; - private Task? _task; + private Task _task; public ConstantValueSource(T value) { @@ -26,7 +23,7 @@ public override T GetValue(CancellationToken cancellationToken = default) return _value; } - public override bool TryGetValue([MaybeNullWhen(false)]out T value) + public override bool TryGetValue(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 cfdc7341f4ac1..ee7e4ec5ca06a 100644 --- a/src/Workspaces/Core/Portable/Utilities/ValuesSources/ValueSource.cs +++ b/src/Workspaces/Core/Portable/Utilities/ValuesSources/ValueSource.cs @@ -1,8 +1,5 @@ // 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; @@ -13,12 +10,18 @@ namespace Roslyn.Utilities /// internal abstract class ValueSource { - public abstract bool TryGetValue([MaybeNullWhen(false)]out T value); + public abstract bool TryGetValue(out T value); public abstract T GetValue(CancellationToken cancellationToken = default); public abstract Task GetValueAsync(CancellationToken cancellationToken = default); - public bool HasValue => TryGetValue(out _); + public bool HasValue + { + get + { + return this.TryGetValue(out var tmp); + } + } - 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 fe9ab2ed223ca..c848705bf7fcf 100644 --- a/src/Workspaces/Core/Portable/Workspace/FileTextLoader.cs +++ b/src/Workspaces/Core/Portable/Workspace/FileTextLoader.cs @@ -80,8 +80,6 @@ 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 4eadbc97caf45..ffe54117645f8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -666,16 +666,6 @@ private static (ValueSource, TreeAndVersion) CreateRecoverableTe return (lazyTextAndVersion, TreeAndVersion.Create(tree, treeVersion)); } - internal override Task GetLoadDiagnosticAsync(CancellationToken cancellationToken) - { - if (TextAndVersionSource is TreeTextSource) - { - return SpecializedTasks.Default(); - } - - 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 59f3a050a8e04..db2c97496e792 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs @@ -1,8 +1,5 @@ // 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; @@ -19,13 +16,12 @@ internal class RecoverableTextAndVersion : ValueSource, ITextVer { private readonly ITemporaryStorageService _storageService; - private SemaphoreSlim? _lazyGate; - private ValueSource? _initialSource; + private SemaphoreSlim _gateDoNotAccessDirectly; // Lazily created. Access via the Gate property + private ValueSource _initialSource; - private RecoverableText? _text; + private RecoverableText _text; private VersionStamp _version; - private string? _filePath; - private Diagnostic? _loadDiagnostic; + private string _filePath; public RecoverableTextAndVersion( ValueSource initialTextAndVersion, @@ -35,20 +31,20 @@ public RecoverableTextAndVersion( _storageService = storageService; } - private SemaphoreSlim Gate => LazyInitialization.EnsureInitialized(ref _lazyGate, SemaphoreSlimFactory.Instance); + private SemaphoreSlim Gate => LazyInitialization.EnsureInitialized(ref _gateDoNotAccessDirectly, SemaphoreSlimFactory.Instance); - public ITemporaryTextStorage? Storage => _text?.Storage; + public ITemporaryTextStorage Storage => _text?.Storage; - public override bool TryGetValue([MaybeNullWhen(false)]out TextAndVersion value) + public override bool TryGetValue(out TextAndVersion value) { if (_text != null && _text.TryGetValue(out var text)) { - value = TextAndVersion.Create(text, _version, _filePath, _loadDiagnostic); + value = TextAndVersion.Create(text, _version, _filePath); return true; } else { - value = null!; + value = null; return false; } } @@ -61,7 +57,7 @@ public bool TryGetTextVersion(out VersionStamp version) // then try to get version from cached value. if (version == default) { - if (TryGetValue(out var textAndVersion)) + if (this.TryGetValue(out var textAndVersion)) { version = textAndVersion.Version; } @@ -78,12 +74,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, _loadDiagnostic); + return TextAndVersion.Create(_text.GetValue(cancellationToken), _version, _filePath); } public override async Task GetValueAsync(CancellationToken cancellationToken = default) @@ -94,13 +90,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, _loadDiagnostic); + return TextAndVersion.Create(text, _version, _filePath); } private TextAndVersion InitRecoverable(TextAndVersion textAndVersion) @@ -108,7 +104,6 @@ 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; @@ -117,7 +112,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)) @@ -125,7 +120,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 9ead030e24b72..24ce4d7d155ee 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs @@ -497,25 +497,17 @@ private async Task BuildDeclarationCompilationFromScratchAsync( { var compilation = CreateEmptyCompilation(); - var trees = ArrayBuilder.GetInstance(ProjectState.DocumentIds.Count); + var trees = new SyntaxTree[ProjectState.DocumentIds.Count]; + var index = 0; foreach (var document in this.ProjectState.OrderedDocumentStates) { cancellationToken.ThrowIfCancellationRequested(); - - // 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)); - } + trees[index] = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + index++; } compilation = compilation.AddSyntaxTrees(trees); - - trees.Free(); - - WriteState(new FullDeclarationState(compilation), solution); + this.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 61970d496c9cd..f66ca519678b7 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextAndVersion.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextAndVersion.cs @@ -23,49 +23,32 @@ public sealed class TextAndVersion public VersionStamp Version { get; } /// - /// An optional file path that identifies the origin of the source text. Empty if not available. + /// An optional file path that identifies the origin of the source text /// public string FilePath { get; } - /// - /// 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) + private TextAndVersion(SourceText text, VersionStamp version, string? filePath) { - Text = text; - Version = version; - FilePath = filePath ?? string.Empty; - LoadDiagnostic = loadDiagnostic; + this.Text = text; + this.Version = version; + this.FilePath = filePath ?? string.Empty; } /// - /// Create a new instance. + /// Create a new TextAndVersion 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, loadDiagnostic); + return new TextAndVersion(text, version, filePath); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index 1fd1ddd3b2c48..9202c11b9e9d8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -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. #nullable enable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -69,13 +70,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) + : this( + services, + info.DocumentServiceProvider, + info.Attributes, + sourceText: null, + textAndVersionSource: info.TextLoader != null + ? CreateRecoverableText(info.TextLoader, info.Id, services, reportInvalidDataException: false) : CreateStrongText(TextAndVersion.Create(SourceText.From(string.Empty, Encoding.UTF8), VersionStamp.Default, info.FilePath))) { } @@ -90,11 +91,11 @@ protected static ValueSource CreateStrongText(TextAndVersion tex return new ConstantValueSource(text); } - protected static ValueSource CreateStrongText(TextLoader loader, DocumentId documentId, SolutionServices services) + protected static ValueSource CreateStrongText(TextLoader loader, DocumentId documentId, SolutionServices services, bool reportInvalidDataException) { return new AsyncLazy( - asynchronousComputeFunction: cancellationToken => loader.LoadTextAsync(services.Workspace, documentId, cancellationToken), - synchronousComputeFunction: cancellationToken => loader.LoadTextSynchronously(services.Workspace, documentId, cancellationToken), + asynchronousComputeFunction: c => LoadTextAsync(loader, documentId, services, reportInvalidDataException, c), + synchronousComputeFunction: c => LoadTextSynchronously(loader, documentId, services, reportInvalidDataException, c), cacheResult: true); } @@ -103,16 +104,102 @@ protected static ValueSource CreateRecoverableText(TextAndVersio return new RecoverableTextAndVersion(CreateStrongText(text), services.TemporaryStorage); } - protected static ValueSource CreateRecoverableText(TextLoader loader, DocumentId documentId, SolutionServices services) + protected static ValueSource CreateRecoverableText(TextLoader loader, DocumentId documentId, SolutionServices services, bool reportInvalidDataException) { return new RecoverableTextAndVersion( new AsyncLazy( - asynchronousComputeFunction: cancellationToken => loader.LoadTextAsync(services.Workspace, documentId, cancellationToken), - synchronousComputeFunction: cancellationToken => loader.LoadTextSynchronously(services.Workspace, documentId, cancellationToken), + asynchronousComputeFunction: c => LoadTextAsync(loader, documentId, services, reportInvalidDataException, c), + synchronousComputeFunction: c => LoadTextSynchronously(loader, documentId, services, reportInvalidDataException, c), 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 @@ -244,8 +331,8 @@ public TextDocumentState UpdateText(TextLoader loader, PreservationMode mode) // don't blow up on non-text documents. var newTextSource = mode == PreservationMode.PreserveIdentity - ? CreateStrongText(loader, Id, solutionServices) - : CreateRecoverableText(loader, Id, solutionServices); + ? CreateStrongText(loader, this.Id, this.solutionServices, reportInvalidDataException: false) + : CreateRecoverableText(loader, this.Id, this.solutionServices, reportInvalidDataException: false); return UpdateText(newTextSource, mode, incremental: false); } @@ -272,9 +359,6 @@ 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 fc1af5374e3cf..3b0e915a681b8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextLoader.cs @@ -1,11 +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.ComponentModel; -using System.IO; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Text; @@ -18,120 +13,22 @@ 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. + /// Load a text and a version of the document in the workspace. /// - /// - /// - /// 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 from an already existing source text and version. + /// Creates a new TextLoader from an already existing source text and version. /// public static TextLoader From(TextAndVersion textAndVersion) { @@ -144,12 +41,12 @@ public static TextLoader From(TextAndVersion textAndVersion) } /// - /// Creates a from a and version. + /// Creates a TextLoader from a SourceTextContainer 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) { @@ -159,7 +56,7 @@ public static TextLoader From(SourceTextContainer container, VersionStamp versio return new TextContainerLoader(container, version, filePath); } - private sealed class TextDocumentLoader : TextLoader + private class TextDocumentLoader : TextLoader { private readonly TextAndVersion _textAndVersion; @@ -169,19 +66,23 @@ internal TextDocumentLoader(TextAndVersion textAndVersion) } public override Task LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) - => Task.FromResult(_textAndVersion); + { + return Task.FromResult(_textAndVersion); + } internal override TextAndVersion LoadTextAndVersionSynchronously(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) - => _textAndVersion; + { + return _textAndVersion; + } } - private sealed class TextContainerLoader : TextLoader + private 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; @@ -189,10 +90,14 @@ internal TextContainerLoader(SourceTextContainer container, VersionStamp version } public override Task LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) - => Task.FromResult(LoadTextAndVersionSynchronously(workspace, documentId, cancellationToken)); + { + return Task.FromResult(LoadTextAndVersionSynchronously(workspace, documentId, cancellationToken)); + } internal override TextAndVersion LoadTextAndVersionSynchronously(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken) - => TextAndVersion.Create(_container.CurrentText, _version, _filePath); + { + return TextAndVersion.Create(_container.CurrentText, _version, _filePath); + } } } } diff --git a/src/Workspaces/Core/Portable/Workspace/WorkspaceDiagnosticDescriptors.cs b/src/Workspaces/Core/Portable/Workspace/WorkspaceDiagnosticDescriptors.cs deleted file mode 100644 index ae44a411da837..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/WorkspaceDiagnosticDescriptors.cs +++ /dev/null @@ -1,23 +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. - -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_content_of_source_file_0_1), 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 7956b80894125..3c33e72edcb8d 100644 --- a/src/Workspaces/Core/Portable/WorkspacesResources.Designer.cs +++ b/src/Workspaces/Core/Portable/WorkspacesResources.Designer.cs @@ -781,15 +781,6 @@ internal static string Error { } } - /// - /// Looks up a localized string similar to Error reading content of source file '{0}' -- '{1}'.. - /// - internal static string Error_reading_content_of_source_file_0_1 { - get { - return ResourceManager.GetString("Error_reading_content_of_source_file_0_1", resourceCulture); - } - } - /// /// Looks up a localized string similar to Event. /// @@ -3800,15 +3791,6 @@ 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 e67824919abdd..6ff1b1dd29573 100644 --- a/src/Workspaces/Core/Portable/WorkspacesResources.resx +++ b/src/Workspaces/Core/Portable/WorkspacesResources.resx @@ -168,12 +168,6 @@ 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 d17ea75f9ae4c..4cf6932d99e22 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf @@ -52,11 +52,6 @@ Dokument nepodporuje stromy syntaxe. - - 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ů @@ -1347,11 +1342,6 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou délkou se obv Přidáním importů se do oboru zavede rozšiřující metoda se stejným názvem jako {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 62022a3fbcc0e..fe4715bd71cec 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf @@ -52,11 +52,6 @@ Das Dokument unterstützt keine Syntaxstrukturen. - - Error reading content of source file '{0}' -- '{1}'. - Error reading content of source file '{0}' -- '{1}'. - - Expression-level preferences Einstellungen für Ausdrucksebene @@ -1347,11 +1342,6 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg Durch das Hinzufügen von Importen wird eine Erweiterungsmethode mit dem gleichen Namen wie "{0}" in den Bereich eingeführt. - - 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 a57a5c15c2275..2d0f3e94682d8 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf @@ -52,11 +52,6 @@ El documento no admite árboles de sintaxis - - 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 @@ -1347,11 +1342,6 @@ Las aserciones posteriores positivas de ancho cero se usan normalmente al princi Al agregar importaciones, se incorporará un método de extensión en el ámbito con el mismo nombre que "{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 76e735b2bc792..16857705d21ee 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf @@ -52,11 +52,6 @@ Le document ne prend pas en charge les arborescences de syntaxe - - 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 @@ -1347,11 +1342,6 @@ Les assertions arrière positives de largeur nulle sont généralement utilisée L'ajout d'importations place une méthode d'extension dans l'étendue avec le même nom que « {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 c4023b7d8080f..2ee6c72052af3 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf @@ -52,11 +52,6 @@ Il documento non supporta alberi di sintassi - - Error reading content of source file '{0}' -- '{1}'. - Error reading content of source file '{0}' -- '{1}'. - - Expression-level preferences Preferenze a livello di espressione @@ -1347,11 +1342,6 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all' Quando si aggiungono importazioni, nell'ambito verrà aggiunto un metodo di estensione con lo stesso nome di '{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 4c188ae39f07f..1f240c5ce3b01 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf @@ -52,11 +52,6 @@ ドキュメントでは構文ツリーがサポートされません - - Error reading content of source file '{0}' -- '{1}'. - Error reading content of source file '{0}' -- '{1}'. - - Expression-level preferences 式レベルの設定 @@ -1347,11 +1342,6 @@ Zero-width positive lookbehind assertions are typically used at the beginning of インポートを追加すると、'{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 2abdf8fb553d0..bc6e5fae9a62f 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf @@ -52,11 +52,6 @@ 문서가 구문 트리를 지원하지 않음 - - Error reading content of source file '{0}' -- '{1}'. - Error reading content of source file '{0}' -- '{1}'. - - Expression-level preferences 식 수준 기본 설정 @@ -1347,11 +1342,6 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 가져오기를 추가하면 '{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 0571630dcb95a..59a9b5dac4aa9 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf @@ -52,11 +52,6 @@ Dokument nie obsługuje drzew składni - - Error reading content of source file '{0}' -- '{1}'. - Error reading content of source file '{0}' -- '{1}'. - - Expression-level preferences Preferencje na poziomie wyrażeń @@ -1347,11 +1342,6 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk Dodanie importów spowoduje wprowadzenie metody rozszerzenia do zakresu o takiej samej nazwie jak „{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 f6da3b8e9763e..fdc55f2776f78 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf @@ -52,11 +52,6 @@ O documento não dá suporte a árvores de sintaxe - - 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 @@ -1347,11 +1342,6 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas A adição de importações inserirá um método de extensão ao escopo com o mesmo nome de '{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 ebd54c81223b6..0c00a5f968ee8 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf @@ -52,11 +52,6 @@ Документ не поддерживает синтаксические деревья. - - Error reading content of source file '{0}' -- '{1}'. - Error reading content of source file '{0}' -- '{1}'. - - Expression-level preferences Выражения уровень предпочтения @@ -1347,11 +1342,6 @@ Zero-width positive lookbehind assertions are typically used at the beginning of При добавлении импортируемых компонентов в область будет добавлен метод расширения с тем же именем, что и "{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 bab9f29518947..f4719680d0f1c 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf @@ -52,11 +52,6 @@ Belge, söz dizimi ağaçlarını desteklemiyor - - Error reading content of source file '{0}' -- '{1}'. - Error reading content of source file '{0}' -- '{1}'. - - Expression-level preferences İfade düzey tercihleri @@ -1347,11 +1342,6 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri İçeri aktarmalar eklemek, bir genişletme yöntemini '{0}' ile aynı ada sahip kapsam içine alacaktır - - 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 b32265cf9c1a8..8094a72425253 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf @@ -52,11 +52,6 @@ 文档不支持语法树 - - Error reading content of source file '{0}' -- '{1}'. - Error reading content of source file '{0}' -- '{1}'. - - Expression-level preferences 表达式级首选项 @@ -1347,11 +1342,6 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 添加导入将使扩展方法与 "{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 df899cc7e6c29..9214e57881604 100644 --- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf @@ -52,11 +52,6 @@ 文件不支援語法樹 - - Error reading content of source file '{0}' -- '{1}'. - Error reading content of source file '{0}' -- '{1}'. - - Expression-level preferences 運算式層級喜好設定 @@ -1347,11 +1342,6 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 新增匯入會讓擴充方法變成名稱與 '{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 a17e4aeeefc20..af3fe6ea01c18 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -1242,34 +1242,76 @@ 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 async Task TestDocumentFileAccessFailureMissingFile() + public void TestWorkspaceDiagnosticHasDebuggerText() { - var workspace = new AdhocWorkspace(); - var solution = workspace.CurrentSolution; + var solution = new AdhocWorkspace().CurrentSolution; + + WorkspaceDiagnostic diagnostic = null; - WorkspaceDiagnostic diagnosticFromEvent = null; solution.Workspace.WorkspaceFailed += (sender, args) => { - diagnosticFromEvent = args.Diagnostic; + 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)) - .WithDocumentFilePath(did, "document path"); + .AddDocument(did, "x", new FileTextLoader(@"C:\doesnotexist.cs", Encoding.UTF8)); var doc = solution.GetDocument(did); - var text = await doc.GetTextAsync().ConfigureAwait(false); + var text = doc.GetTextAsync().Result; + + WaitFor(() => diagnostic != null, TimeSpan.FromSeconds(5)); - var diagnostic = await doc.State.GetLoadDiagnosticAsync(CancellationToken.None).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)); + } - Assert.Equal(@"C:\doesnotexist.cs: (0,0)-(0,0)", diagnostic.Location.GetLineSpan().ToString()); - Assert.Equal(WorkspaceDiagnosticKind.Failure, diagnosticFromEvent.Kind); - Assert.Equal("", text.ToString()); + return condition(); } [Fact] diff --git a/src/Workspaces/CoreTestUtilities/WorkspaceExtensions.cs b/src/Workspaces/CoreTestUtilities/WorkspaceExtensions.cs index fcb3917d28743..0930ee5425f05 100644 --- a/src/Workspaces/CoreTestUtilities/WorkspaceExtensions.cs +++ b/src/Workspaces/CoreTestUtilities/WorkspaceExtensions.cs @@ -60,5 +60,12 @@ 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; + } } } diff --git a/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs b/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs index 6774689849559..3e30599ddf99c 100644 --- a/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs +++ b/src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs @@ -946,6 +946,12 @@ 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); @@ -958,7 +964,7 @@ public async Task TestOpenSolution_WithTemporaryLockedFile_SucceedsWithoutFailur var getTextTask = doc.GetTextAsync(); // wait 1 unit of retry delay then close file - var delay = TextLoader.RetryDelay; + var delay = TextDocumentState.RetryDelay; await Task.Delay(delay).ContinueWith(t => file.Close(), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); // finish reading text @@ -970,7 +976,7 @@ public async Task TestOpenSolution_WithTemporaryLockedFile_SucceedsWithoutFailur file.Close(); } - Assert.Empty(ws.Diagnostics); + Assert.False(failed); } } @@ -984,6 +990,12 @@ 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); @@ -999,10 +1011,11 @@ public async Task TestOpenSolution_WithLockedFile_FailsWithFailureEvent() file.Close(); } - Assert.Equal(WorkspaceDiagnosticKind.Failure, workspace.Diagnostics.Single().Kind); + Assert.True(failed); } } + [ConditionalFact(typeof(VisualStudioMSBuildInstalled)), Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)] public async Task TestOpenSolution_WithInvalidProjectPath_SkipTrue_SucceedsWithFailureEvent() { @@ -1015,8 +1028,15 @@ 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(workspace.Diagnostics); + + Assert.Single(diagnostics); } } @@ -1030,8 +1050,15 @@ 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(workspace.Diagnostics); + + Assert.Empty(diagnostics); } } @@ -1066,9 +1093,15 @@ 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(workspace.Diagnostics); + Assert.Single(diagnostics); } } @@ -1135,9 +1168,15 @@ 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(workspace.Diagnostics); + Assert.Single(diagnostics); Assert.Empty(solution.ProjectIds); } } @@ -1207,11 +1246,19 @@ 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, workspace.Diagnostics.Single().Message); + Assert.Equal(expected, diagnostics[0].Message); } } @@ -1276,13 +1323,17 @@ 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.NotEmpty(workspace.Diagnostics); } } @@ -1312,13 +1363,17 @@ 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.NotEmpty(workspace.Diagnostics); } } @@ -1349,13 +1404,17 @@ 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.NotEmpty(workspace.Diagnostics); } }