diff --git a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs index 3ccc91692fde1..af03e0d23f713 100644 --- a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs +++ b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.cs @@ -34,7 +34,8 @@ public void DiagnosticAnalyzerAllInOne() missingSyntaxKinds.Add(SyntaxKind.FunctionPointerType); var analyzer = new CSharpTrackingDiagnosticAnalyzer(); - CreateCompilationWithMscorlib45(source).VerifyAnalyzerDiagnostics(new[] { analyzer }); + var options = new AnalyzerOptions(new[] { new TestAdditionalText() }.ToImmutableArray()); + CreateCompilationWithMscorlib45(source).VerifyAnalyzerDiagnostics(new[] { analyzer }, options); analyzer.VerifyAllAnalyzerMembersWereCalled(); analyzer.VerifyAnalyzeSymbolCalledForAllSymbolKinds(); analyzer.VerifyAnalyzeNodeCalledForAllSyntaxKinds(missingSyntaxKinds); diff --git a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb index afbe014eabcac..d246cc377540d 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.AllInOne.vb @@ -19,7 +19,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Semantics Public Sub DiagnosticAnalyzerAllInOne() Dim source = TestResource.AllInOneVisualBasicBaseline Dim analyzer = New BasicTrackingDiagnosticAnalyzer() - CreateCompilationWithMscorlib40({source}).VerifyAnalyzerDiagnostics({analyzer}) + Dim options = New AnalyzerOptions({DirectCast(New TestAdditionalText(), AdditionalText)}.ToImmutableArray()) + CreateCompilationWithMscorlib40({source}).VerifyAnalyzerDiagnostics({analyzer}, options) analyzer.VerifyAllAnalyzerMembersWereCalled() analyzer.VerifyAnalyzeSymbolCalledForAllSymbolKinds() analyzer.VerifyAnalyzeNodeCalledForAllSyntaxKinds(New HashSet(Of SyntaxKind)()) diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs index 691120f393770..c779d57265197 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs @@ -50,7 +50,9 @@ public async Task DiagnosticAnalyzerDriverAllInOne() using var workspace = TestWorkspace.CreateCSharp(source, TestOptions.Regular); var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create(analyzer)); - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })); + var newSolution = workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference }) + .Projects.Single().AddAdditionalDocument(name: "dummy.txt", text: "", filePath: "dummy.txt").Project.Solution; + workspace.TryApplyChanges(newSolution); var document = workspace.CurrentSolution.Projects.Single().Documents.Single(); AccessSupportedDiagnostics(analyzer); diff --git a/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs b/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs index 8ae114b651574..d41db3f891a5a 100644 --- a/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs +++ b/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; using Roslyn.Utilities; @@ -73,7 +74,7 @@ public void Register(Workspace workspace) private async Task AnalyzeAsync() { var workerBackOffTimeSpanInMS = _workspace.Options.GetOption(InternalSolutionCrawlerOptions.PreviewBackOffTimeSpanInMS); - var diagnosticAnalyzer = _owner._analyzerService.CreateIncrementalAnalyzer(_workspace); + var incrementalAnalyzer = _owner._analyzerService.CreateIncrementalAnalyzer(_workspace); var solution = _workspace.CurrentSolution; var documentIds = _workspace.GetOpenDocumentIds().ToImmutableArray(); @@ -82,8 +83,8 @@ private async Task AnalyzeAsync() { foreach (var documentId in documentIds) { - var document = solution.GetDocument(documentId); - if (document == null) + var textDocument = solution.GetTextDocument(documentId); + if (textDocument == null) { continue; } @@ -92,8 +93,15 @@ private async Task AnalyzeAsync() await Task.Delay(workerBackOffTimeSpanInMS, _source.Token).ConfigureAwait(false); // do actual analysis - await diagnosticAnalyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, _source.Token).ConfigureAwait(false); - await diagnosticAnalyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: _source.Token).ConfigureAwait(false); + if (textDocument is Document document) + { + await incrementalAnalyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, _source.Token).ConfigureAwait(false); + await incrementalAnalyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: _source.Token).ConfigureAwait(false); + } + else if (incrementalAnalyzer is IIncrementalAnalyzer2 incrementalAnalyzer2) + { + await incrementalAnalyzer2.AnalyzeNonSourceDocumentAsync(textDocument, InvocationReasons.Empty, _source.Token).ConfigureAwait(false); + } // don't call project one. } diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 28e04485a4286..1b4bac408eed9 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -564,6 +565,105 @@ private async Task TestFullSolutionAnalysisForProjectAsync(Project project, bool Assert.Equal(expectAnalyzerExecuted, called); } + [Theory, CombinatorialData] + internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool testMultiple, BackgroundAnalysisScope analysisScope) + { + using var workspace = new AdhocWorkspace(); + var options = workspace.Options.WithChangedOption(SolutionCrawlerOptions.BackgroundAnalysisScopeOption, LanguageNames.CSharp, analysisScope); + workspace.SetOptions(options); + + var projectInfo = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), "CSharpProject", "CSharpProject", LanguageNames.CSharp); + var project = workspace.AddProject(projectInfo); + + var diagnosticSpan = new TextSpan(2, 2); + var analyzer = new AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan, id: "ID0001"); + var analyzers = ImmutableArray.Create(analyzer); + if (testMultiple) + { + analyzer = new AdditionalFileAnalyzer2(registerFromInitialize, diagnosticSpan, id: "ID0002"); + analyzers = analyzers.Add(analyzer); + } + + var analyzerReference = new AnalyzerImageReference(analyzers); + project = project.WithAnalyzerReferences(new[] { analyzerReference }) + .AddAdditionalDocument(name: "dummy.txt", text: "Additional File Text", filePath: "dummy.txt").Project; + if (testMultiple) + { + project = project.AddAdditionalDocument(name: "dummy2.txt", text: "Additional File2 Text", filePath: "dummy2.txt").Project; + } + + var applied = workspace.TryApplyChanges(project.Solution); + Assert.True(applied); + + // create listener/service/analyzer + var listener = new AsynchronousOperationListener(); + var service = new MyDiagnosticAnalyzerService(listener); + + var diagnostics = new ConcurrentSet(); + service.DiagnosticsUpdated += (s, e) => + { + diagnostics.AddRange(e.Diagnostics); + }; + + var incrementalAnalyzer = (DiagnosticIncrementalAnalyzer)service.CreateIncrementalAnalyzer(workspace); + var firstAdditionalDocument = project.AdditionalDocuments.FirstOrDefault(); + + switch (analysisScope) + { + case BackgroundAnalysisScope.ActiveFile: + case BackgroundAnalysisScope.OpenFilesAndProjects: + workspace.OpenAdditionalDocument(firstAdditionalDocument.Id); + await incrementalAnalyzer.AnalyzeNonSourceDocumentAsync(firstAdditionalDocument, InvocationReasons.SyntaxChanged, CancellationToken.None); + break; + + case BackgroundAnalysisScope.FullSolution: + await incrementalAnalyzer.AnalyzeProjectAsync(project, semanticsChanged: true, InvocationReasons.Reanalyze, CancellationToken.None); + break; + + default: + throw ExceptionUtilities.UnexpectedValue(analysisScope); + } + + await listener.ExpeditedWaitAsync(); + + var expectedCount = !testMultiple + ? 1 + : analysisScope == BackgroundAnalysisScope.FullSolution ? 4 : 2; + Assert.Equal(expectedCount, diagnostics.Count); + + for (var i = 0; i < analyzers.Length; i++) + { + analyzer = (AdditionalFileAnalyzer)analyzers[i]; + foreach (var additionalDoc in project.AdditionalDocuments) + { + var applicableDiagnostics = diagnostics.Where( + d => d.Id == analyzer.Descriptor.Id && d.DataLocation.OriginalFilePath == additionalDoc.FilePath); + + if (analysisScope != BackgroundAnalysisScope.FullSolution && + firstAdditionalDocument != additionalDoc) + { + Assert.Empty(applicableDiagnostics); + } + else + { + var diagnostic = Assert.Single(applicableDiagnostics); + Assert.Equal(diagnosticSpan, diagnostic.GetTextSpan()); + diagnostics.Remove(diagnostic); + } + } + } + + Assert.Empty(diagnostics); + } + + private class AdditionalFileAnalyzer2 : AdditionalFileAnalyzer + { + public AdditionalFileAnalyzer2(bool registerFromInitialize, TextSpan diagnosticSpan, string id) + : base(registerFromInitialize, diagnosticSpan, id) + { + } + } + [Theory, CombinatorialData] internal async Task TestDiagnosticSuppressor(bool includeAnalyzer, bool includeSuppressor, BackgroundAnalysisScope analysisScope) { @@ -678,11 +778,19 @@ private static (bool, bool) CompilerAnalyzerResultSetter(bool syntax, bool seman return (syntax, semantic); } - private static async Task RunAllAnalysisAsync(IIncrementalAnalyzer analyzer, Document document) + private static async Task RunAllAnalysisAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument) { - await analyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, CancellationToken.None).ConfigureAwait(false); - await analyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false); - await analyzer.AnalyzeProjectAsync(document.Project, semanticsChanged: true, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false); + if (textDocument is Document document) + { + await analyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, CancellationToken.None).ConfigureAwait(false); + await analyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.AnalyzeNonSourceDocumentAsync(textDocument, InvocationReasons.Empty, CancellationToken.None).ConfigureAwait(false); + } + + await analyzer.AnalyzeProjectAsync(textDocument.Project, semanticsChanged: true, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false); } private class MyDiagnosticAnalyzerService : DiagnosticAnalyzerService diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs index 28e91578978c7..47af60fbe9bd4 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs @@ -307,8 +307,7 @@ protected override void ApplyDocumentAdded(DocumentInfo info, SourceText text) var hostProject = this.GetTestProject(info.Id.ProjectId); var hostDocument = new TestHostDocument( text.ToString(), info.Name, info.SourceCodeKind, - info.Id, folders: info.Folders, - exportProvider: ExportProvider); + info.Id, folders: info.Folders, exportProvider: ExportProvider); hostProject.AddDocument(hostDocument); this.OnDocumentAdded(hostDocument.ToDocumentInfo()); } @@ -330,7 +329,7 @@ protected override void ApplyAdditionalDocumentTextChanged(DocumentId document, protected override void ApplyAdditionalDocumentAdded(DocumentInfo info, SourceText text) { var hostProject = this.GetTestProject(info.Id.ProjectId); - var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id); + var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id, exportProvider: ExportProvider); hostProject.AddAdditionalDocument(hostDocument); this.OnAdditionalDocumentAdded(hostDocument.ToDocumentInfo()); } @@ -352,7 +351,7 @@ protected override void ApplyAnalyzerConfigDocumentTextChanged(DocumentId docume protected override void ApplyAnalyzerConfigDocumentAdded(DocumentInfo info, SourceText text) { var hostProject = this.GetTestProject(info.Id.ProjectId); - var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id, filePath: info.FilePath, folders: info.Folders); + var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id, filePath: info.FilePath, folders: info.Folders, exportProvider: ExportProvider); hostProject.AddAnalyzerConfigDocument(hostDocument); this.OnAnalyzerConfigDocumentAdded(hostDocument.ToDocumentInfo()); } diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb index 7dbf5e70b41b8..a218a6b30ac51 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.vb @@ -17,7 +17,9 @@ Public Class DiagnosticAnalyzerDriverTests Dim analyzer = New BasicTrackingDiagnosticAnalyzer() Using workspace = TestWorkspace.CreateVisualBasic(source) Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) - workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference})) + Dim newSolution = workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference}). + Projects.Single().AddAdditionalDocument(name:="dummy.txt", text:="", filePath:="dummy.txt").Project.Solution + workspace.TryApplyChanges(newSolution) Dim document = workspace.CurrentSolution.Projects.Single().Documents.Single() AccessSupportedDiagnostics(analyzer) diff --git a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs index c7237894f9ede..1dd1b7d98dcf8 100644 --- a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs +++ b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs @@ -303,19 +303,22 @@ private static void AssertCompilation(Project project, Compilation compilation1) /// public static async Task> ComputeDiagnosticsAsync( DiagnosticAnalyzer analyzer, - Document document, + TextDocument textDocument, AnalysisKind kind, DiagnosticAnalyzerInfoCache analyzerInfoCache, CompilationWithAnalyzers? compilationWithAnalyzers, TextSpan? span, CancellationToken cancellationToken) { - var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); + var document = textDocument as Document; + RoslynDebug.Assert(document != null || kind == AnalysisKind.Syntax, "We only support syntactic analysis for non-source documents"); + + var loadDiagnostic = await textDocument.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); if (analyzer == FileContentLoadAnalyzer.Instance) { return loadDiagnostic != null ? - SpecializedCollections.SingletonEnumerable(DiagnosticData.Create(loadDiagnostic, document)) : + SpecializedCollections.SingletonEnumerable(DiagnosticData.Create(loadDiagnostic, textDocument)) : SpecializedCollections.EmptyEnumerable(); } @@ -324,9 +327,11 @@ public static async Task> ComputeDiagnosticsAsync( return SpecializedCollections.EmptyEnumerable(); } - if (analyzer is DocumentDiagnosticAnalyzer documentAnalyzer) + ImmutableArray diagnostics; + if (document != null && + analyzer is DocumentDiagnosticAnalyzer documentAnalyzer) { - var diagnostics = await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( + diagnostics = await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( documentAnalyzer, document, kind, compilationWithAnalyzers?.Compilation, cancellationToken).ConfigureAwait(false); return diagnostics.ConvertToLocalDiagnostics(document); @@ -338,14 +343,14 @@ public static async Task> ComputeDiagnosticsAsync( if (kind == AnalysisKind.Syntax) { Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, - (r, d, a, k) => $"Driver: {r != null}, {d.Id}, {d.Project.Id}, {a}, {k}", compilationWithAnalyzers, document, analyzer, kind); + (r, d, a, k) => $"Driver: {r != null}, {d.Id}, {d.Project.Id}, {a}, {k}", compilationWithAnalyzers, textDocument, analyzer, kind); } return SpecializedCollections.EmptyEnumerable(); } // if project is not loaded successfully then, we disable semantic errors for compiler analyzers - if (kind != AnalysisKind.Syntax && analyzer.IsCompilerAnalyzer()) + if (kind != AnalysisKind.Syntax && analyzer.IsCompilerAnalyzer() && document != null) { var isEnabled = await document.Project.HasSuccessfullyLoadedAsync(cancellationToken).ConfigureAwait(false); @@ -359,52 +364,61 @@ public static async Task> ComputeDiagnosticsAsync( // REVIEW: more unnecessary allocations just to get diagnostics per analyzer var singleAnalyzer = ImmutableArray.Create(analyzer); - var skippedAnalyzerInfo = document.Project.GetSkippedAnalyzersInfo(analyzerInfoCache); + var skippedAnalyzerInfo = textDocument.Project.GetSkippedAnalyzersInfo(analyzerInfoCache); ImmutableArray filteredIds; switch (kind) { case AnalysisKind.Syntax: - var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - if (tree == null) + if (document != null) { - return SpecializedCollections.EmptyEnumerable(); - } - - var diagnostics = await compilationWithAnalyzers.GetAnalyzerSyntaxDiagnosticsAsync(tree, singleAnalyzer, cancellationToken).ConfigureAwait(false); + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + if (tree == null) + { + return SpecializedCollections.EmptyEnumerable(); + } - if (diagnostics.IsDefaultOrEmpty) - { - Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, (d, a, t) => $"{d.Id}, {d.Project.Id}, {a}, {t.Length}", document, analyzer, tree); + diagnostics = await compilationWithAnalyzers.GetAnalyzerSyntaxDiagnosticsAsync(tree, singleAnalyzer, cancellationToken).ConfigureAwait(false); } - else if (skippedAnalyzerInfo.FilteredDiagnosticIdsForAnalyzers.TryGetValue(analyzer, out filteredIds)) + else { - diagnostics = diagnostics.Filter(filteredIds); - } + // Currently, we only support analysis for additional documents. In future, we may support analyzer config documents. + if (textDocument.Kind == TextDocumentKind.AdditionalDocument) + { + var filePath = textDocument.FilePath ?? textDocument.Name; + var additionalFile = compilationWithAnalyzers.AnalysisOptions.Options?.AdditionalFiles.FirstOrDefault(a => PathUtilities.Comparer.Equals(a.Path, filePath)); + if (additionalFile != null) + { + diagnostics = await compilationWithAnalyzers.GetAnalyzerAdditionalFileDiagnosticsAsync(additionalFile, singleAnalyzer, cancellationToken).ConfigureAwait(false); + break; + } + } - Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationWithAnalyzers.Compilation).Count()); - return diagnostics.ConvertToLocalDiagnostics(document); + return SpecializedCollections.EmptyEnumerable(); + } + break; case AnalysisKind.Semantic: - var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var model = await document!.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (model == null) { return SpecializedCollections.EmptyEnumerable(); } diagnostics = await compilationWithAnalyzers.GetAnalyzerSemanticDiagnosticsAsync(model, span, singleAnalyzer, cancellationToken).ConfigureAwait(false); - - if (skippedAnalyzerInfo.FilteredDiagnosticIdsForAnalyzers.TryGetValue(analyzer, out filteredIds)) - { - diagnostics = diagnostics.Filter(filteredIds); - } - - Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationWithAnalyzers.Compilation).Count()); - return diagnostics.ConvertToLocalDiagnostics(document); + break; default: throw ExceptionUtilities.UnexpectedValue(kind); } + + if (skippedAnalyzerInfo.FilteredDiagnosticIdsForAnalyzers.TryGetValue(analyzer, out filteredIds)) + { + diagnostics = diagnostics.Filter(filteredIds); + } + + Debug.Assert(diagnostics.Length == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationWithAnalyzers.Compilation).Count()); + return diagnostics.ConvertToLocalDiagnostics(textDocument); } public static async Task> ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( @@ -567,60 +581,38 @@ async Task VerifyDiagnosticLocationAsync(string id, Location location) } #endif - public static IEnumerable ConvertToLocalDiagnostics(this IEnumerable diagnostics, Document targetDocument, TextSpan? span = null) + public static IEnumerable ConvertToLocalDiagnostics(this IEnumerable diagnostics, TextDocument targetDocument, TextSpan? span = null) { - var project = targetDocument.Project; - - if (project.SupportsCompilation) - { - return ConvertToLocalDiagnosticsWithCompilation(); - } - - return ConvertToLocalDiagnosticsWithoutCompilation(); - - IEnumerable ConvertToLocalDiagnosticsWithoutCompilation() + foreach (var diagnostic in diagnostics) { - Contract.ThrowIfTrue(project.SupportsCompilation); - - foreach (var diagnostic in diagnostics) + if (!IsReportedInDocument(diagnostic, targetDocument)) { - var location = diagnostic.Location; - if (location.Kind != LocationKind.ExternalFile) - { - continue; - } - - var lineSpan = location.GetLineSpan(); - - var documentIds = project.Solution.GetDocumentIdsWithFilePath(lineSpan.Path); - if (documentIds.IsEmpty || documentIds.All(id => id != targetDocument.Id)) - { - continue; - } + continue; + } - yield return DiagnosticData.Create(diagnostic, targetDocument); + if (span.HasValue && !span.Value.IntersectsWith(diagnostic.Location.SourceSpan)) + { + continue; } + + yield return DiagnosticData.Create(diagnostic, targetDocument); } - IEnumerable ConvertToLocalDiagnosticsWithCompilation() + static bool IsReportedInDocument(Diagnostic diagnostic, TextDocument targetDocument) { - Contract.ThrowIfFalse(project.SupportsCompilation); - - foreach (var diagnostic in diagnostics) + if (diagnostic.Location.SourceTree != null) { - var document = project.GetDocument(diagnostic.Location.SourceTree); - if (document == null || document != targetDocument) - { - continue; - } - - if (span.HasValue && !span.Value.IntersectsWith(diagnostic.Location.SourceSpan)) - { - continue; - } + return targetDocument.Project.GetDocument(diagnostic.Location.SourceTree) == targetDocument; + } + else if (diagnostic.Location.Kind == LocationKind.ExternalFile) + { + var lineSpan = diagnostic.Location.GetLineSpan(); - yield return DiagnosticData.Create(diagnostic, document); + var documentIds = targetDocument.Project.Solution.GetDocumentIdsWithFilePath(lineSpan.Path); + return documentIds.Any(id => id == targetDocument.Id); } + + return false; } } @@ -708,6 +700,7 @@ public override void RegisterOperationAction(Action ac public override void RegisterOperationBlockAction(Action action) { } public override void RegisterOperationBlockStartAction(Action action) { } public override void RegisterSymbolStartAction(Action action, SymbolKind symbolKind) { } + public override void RegisterAdditionalFileAction(Action action) { } #endregion private class CollectNestedCompilationContext : CompilationStartAnalysisContext @@ -740,6 +733,7 @@ public override void RegisterOperationAction(Action ac public override void RegisterOperationBlockAction(Action action) { } public override void RegisterOperationBlockStartAction(Action action) { } public override void RegisterSymbolStartAction(Action action, SymbolKind symbolKind) { } + public override void RegisterAdditionalFileAction(Action action) { } #endregion } } diff --git a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs index 127f4d84c5e10..e34b02c4e4c7e 100644 --- a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs @@ -59,7 +59,7 @@ public ImmutableArray GetDiagnostics(Workspace workspace, Projec internal void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs state) => DiagnosticsUpdated?.Invoke(this, state); - private class DefaultDiagnosticIncrementalAnalyzer : IIncrementalAnalyzer + private class DefaultDiagnosticIncrementalAnalyzer : IIncrementalAnalyzer2 { private readonly DefaultDiagnosticAnalyzerService _service; private readonly Workspace _workspace; @@ -82,7 +82,13 @@ public bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs return false; } - public async Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, CancellationToken cancellationToken) + public Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, CancellationToken cancellationToken) + => AnalyzeSyntaxOrNonSourceDocumentAsync(document, cancellationToken); + + public Task AnalyzeNonSourceDocumentAsync(TextDocument document, InvocationReasons reasons, CancellationToken cancellationToken) + => AnalyzeSyntaxOrNonSourceDocumentAsync(document, cancellationToken); + + private async Task AnalyzeSyntaxOrNonSourceDocumentAsync(TextDocument document, CancellationToken cancellationToken) { Debug.Assert(document.Project.Solution.Workspace == _workspace); @@ -124,7 +130,7 @@ bool IsSemanticAnalysisOn() } } - private async Task AnalyzeForKindAsync(Document document, AnalysisKind kind, CancellationToken cancellationToken) + private async Task AnalyzeForKindAsync(TextDocument document, AnalysisKind kind, CancellationToken cancellationToken) { var diagnosticData = await GetDiagnosticsAsync(document, kind, cancellationToken).ConfigureAwait(false); @@ -146,7 +152,7 @@ private async Task AnalyzeForKindAsync(Document document, AnalysisKind kind, Can /// that provide all kinds of knobs/cache/persistency/OOP to get better perf over simplicity. /// private async Task> GetDiagnosticsAsync( - Document document, AnalysisKind kind, CancellationToken cancellationToken) + TextDocument document, AnalysisKind kind, CancellationToken cancellationToken) { var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false); if (loadDiagnostic != null) @@ -203,9 +209,18 @@ public Task DocumentResetAsync(Document document, CancellationToken cancellation return RemoveDocumentAsync(document.Id, cancellationToken); } + public Task NonSourceDocumentResetAsync(TextDocument document, CancellationToken cancellationToken) + { + // no closed file diagnostic and file is not opened, remove any existing diagnostics + return RemoveDocumentAsync(document.Id, cancellationToken); + } + public Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) => DocumentResetAsync(document, cancellationToken); + public Task NonSourceDocumentCloseAsync(TextDocument document, CancellationToken cancellationToken) + => NonSourceDocumentResetAsync(document, cancellationToken); + private void RaiseEmptyDiagnosticUpdated(AnalysisKind kind, DocumentId documentId) { _service.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved( @@ -218,6 +233,9 @@ public Task AnalyzeProjectAsync(Project project, bool semanticsChanged, Invocati public Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) => Task.CompletedTask; + public Task NonSourceDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken) + => Task.CompletedTask; + public Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken) => Task.CompletedTask; diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerTelemetry.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerTelemetry.cs index 246697b293e36..c7b55e5a38110 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerTelemetry.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerTelemetry.cs @@ -23,6 +23,7 @@ private readonly struct Data public readonly int CompilationEndActionsCount; public readonly int CompilationActionsCount; public readonly int SyntaxTreeActionsCount; + public readonly int AdditionalFileActionsCount; public readonly int SemanticModelActionsCount; public readonly int SymbolActionsCount; public readonly int SymbolStartActionsCount; @@ -51,6 +52,7 @@ public Data(AnalyzerTelemetryInfo analyzerTelemetryInfo, bool isTelemetryCollect SymbolActionsCount = analyzerTelemetryInfo.SymbolActionsCount; SyntaxNodeActionsCount = analyzerTelemetryInfo.SyntaxNodeActionsCount; SyntaxTreeActionsCount = analyzerTelemetryInfo.SyntaxTreeActionsCount; + AdditionalFileActionsCount = analyzerTelemetryInfo.AdditionalFileActionsCount; OperationActionsCount = analyzerTelemetryInfo.OperationActionsCount; OperationBlockActionsCount = analyzerTelemetryInfo.OperationBlockActionsCount; OperationBlockEndActionsCount = analyzerTelemetryInfo.OperationBlockEndActionsCount; @@ -114,6 +116,7 @@ public void ReportAndClear(int correlationId) m["Analyzer.SemanticModel"] = analyzerInfo.SemanticModelActionsCount; m["Analyzer.SyntaxNode"] = analyzerInfo.SyntaxNodeActionsCount; m["Analyzer.SyntaxTree"] = analyzerInfo.SyntaxTreeActionsCount; + m["Analyzer.AdditionalFile"] = analyzerInfo.AdditionalFileActionsCount; m["Analyzer.Operation"] = analyzerInfo.OperationActionsCount; m["Analyzer.OperationBlock"] = analyzerInfo.OperationBlockActionsCount; m["Analyzer.OperationBlockStart"] = analyzerInfo.OperationBlockStartActionsCount; diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs index af68a61c56533..7b00eba9f8ff2 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticResultSerializer.cs @@ -148,6 +148,7 @@ private static void WriteTelemetry(ObjectWriter writer, AnalyzerTelemetryInfo te writer.WriteInt32(telemetryInfo.CompilationEndActionsCount); writer.WriteInt32(telemetryInfo.CompilationActionsCount); writer.WriteInt32(telemetryInfo.SyntaxTreeActionsCount); + writer.WriteInt32(telemetryInfo.AdditionalFileActionsCount); writer.WriteInt32(telemetryInfo.SemanticModelActionsCount); writer.WriteInt32(telemetryInfo.SymbolActionsCount); writer.WriteInt32(telemetryInfo.SymbolStartActionsCount); @@ -173,6 +174,7 @@ private static AnalyzerTelemetryInfo ReadTelemetry(ObjectReader reader, Cancella var compilationEndActionsCount = reader.ReadInt32(); var compilationActionsCount = reader.ReadInt32(); var syntaxTreeActionsCount = reader.ReadInt32(); + var additionalFileActionsCount = reader.ReadInt32(); var semanticModelActionsCount = reader.ReadInt32(); var symbolActionsCount = reader.ReadInt32(); var symbolStartActionsCount = reader.ReadInt32(); @@ -196,6 +198,7 @@ private static AnalyzerTelemetryInfo ReadTelemetry(ObjectReader reader, Cancella CompilationActionsCount = compilationActionsCount, SyntaxTreeActionsCount = syntaxTreeActionsCount, + AdditionalFileActionsCount = additionalFileActionsCount, SemanticModelActionsCount = semanticModelActionsCount, SymbolActionsCount = symbolActionsCount, SymbolStartActionsCount = symbolStartActionsCount, diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index c308fd4b66447..e946e676d380d 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -26,7 +26,7 @@ internal partial class DiagnosticIncrementalAnalyzer /// Return all local diagnostics (syntax, semantic) that belong to given document for the given StateSet (analyzer) either from cache or by calculating them /// private async Task GetDocumentAnalysisDataAsync( - CompilationWithAnalyzers? compilation, Document document, StateSet stateSet, AnalysisKind kind, CancellationToken cancellationToken) + CompilationWithAnalyzers? compilation, TextDocument document, StateSet stateSet, AnalysisKind kind, CancellationToken cancellationToken) { // get log title and functionId GetLogFunctionIdAndTitle(kind, out var functionId, out var title); diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs index cf79282a1934e..7dcd99ec13024 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Workspaces.Diagnostics; using Roslyn.Utilities; @@ -111,7 +112,7 @@ public async Task GetAnalysisDataAsync(IPersistentStor /// /// Return all diagnostics for the given document stored in this state including non local diagnostics for this document /// - public async Task GetAnalysisDataAsync(IPersistentStorageService persistentService, Document document, bool avoidLoadingData, CancellationToken cancellationToken) + public async Task GetAnalysisDataAsync(IPersistentStorageService persistentService, TextDocument document, bool avoidLoadingData, CancellationToken cancellationToken) { // make a copy of last result. var lastResult = _lastResult; @@ -202,7 +203,7 @@ public async Task SaveAsync(IPersistentStorageService persistentService, Project var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, result.Version); foreach (var documentId in result.DocumentIds) { - var document = project.GetDocument(documentId); + var document = project.GetTextDocument(documentId); if (document == null) { // it can happen with build synchronization since, in build case, @@ -229,7 +230,7 @@ public void ResetVersion() _lastResult = _lastResult.Reset(); } - public async Task MergeAsync(IPersistentStorageService persistentService, ActiveFileState state, Document document) + public async Task MergeAsync(IPersistentStorageService persistentService, ActiveFileState state, TextDocument document) { Contract.ThrowIfFalse(state.DocumentId == document.Id); @@ -312,7 +313,7 @@ private async Task LoadInitialAnalysisDataAsync(IPersi return builder.ToResult(); } - private async Task LoadInitialAnalysisDataAsync(IPersistentStorageService persistentService, Document document, CancellationToken cancellationToken) + private async Task LoadInitialAnalysisDataAsync(IPersistentStorageService persistentService, TextDocument document, CancellationToken cancellationToken) { // loading data can be cancelled any time. var project = document.Project; @@ -344,7 +345,7 @@ private async Task LoadInitialProjectAnalysisDataAsync return builder.ToResult(); } - private async Task SerializeAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, Document? document, object key, string stateKey, ImmutableArray diagnostics) + private async Task SerializeAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, TextDocument? document, object key, string stateKey, ImmutableArray diagnostics) { Contract.ThrowIfFalse(document == null || document.Project == project); @@ -360,7 +361,7 @@ private async Task SerializeAsync(IPersistentStorageService persistentService, D InMemoryStorage.Cache(_owner.Analyzer, (key, stateKey), new CacheEntry(serializer.Version, diagnostics)); } - private async Task TryDeserializeDocumentDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Document document, Builder builder, CancellationToken cancellationToken) + private async Task TryDeserializeDocumentDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, TextDocument document, Builder builder, CancellationToken cancellationToken) { var success = true; var project = document.Project; @@ -411,7 +412,7 @@ private async Task TryDeserializeProjectDiagnosticsAsync(IPersistentStorag return false; } - private ValueTask> DeserializeDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, Document? document, object key, string stateKey, CancellationToken cancellationToken) + private ValueTask> DeserializeDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, TextDocument? document, object key, string stateKey, CancellationToken cancellationToken) { Contract.ThrowIfFalse(document == null || document.Project == project); diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs index 3b174d098f4d4..a0d338bc57271 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -192,7 +192,7 @@ public ImmutableArray CreateBuildOnlyProjectStateSet(Project project) return stateSets.ToImmutable(); } - public static bool OnDocumentReset(IEnumerable stateSets, Document document) + public static bool OnDocumentReset(IEnumerable stateSets, TextDocument document) { // can not be cancelled var removed = false; @@ -204,7 +204,7 @@ public static bool OnDocumentReset(IEnumerable stateSets, Document doc return removed; } - public async Task OnDocumentOpenedAsync(IEnumerable stateSets, Document document) + public async Task OnDocumentOpenedAsync(IEnumerable stateSets, TextDocument document) { // can not be cancelled var opened = false; @@ -216,7 +216,7 @@ public async Task OnDocumentOpenedAsync(IEnumerable stateSets, D return opened; } - public async Task OnDocumentClosedAsync(IEnumerable stateSets, Document document) + public async Task OnDocumentClosedAsync(IEnumerable stateSets, TextDocument document) { // can not be cancelled var removed = false; diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs index 171823b7fff72..7c4873479858f 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs @@ -138,7 +138,7 @@ public ActiveFileState GetOrCreateActiveFileState(DocumentId documentId) public ProjectState GetOrCreateProjectState(ProjectId projectId) => _projectStates.GetOrAdd(projectId, id => new ProjectState(this, id)); - public async Task OnDocumentOpenedAsync(IPersistentStorageService persistentStorageService, Document document) + public async Task OnDocumentOpenedAsync(IPersistentStorageService persistentStorageService, TextDocument document) { // can not be cancelled if (!TryGetProjectState(document.Project.Id, out var projectState) || @@ -159,7 +159,7 @@ public async Task OnDocumentOpenedAsync(IPersistentStorageService persiste return true; } - public async Task OnDocumentClosedAsync(IPersistentStorageService persistentStorageService, Document document) + public async Task OnDocumentClosedAsync(IPersistentStorageService persistentStorageService, TextDocument document) { // can not be cancelled // remove active file state and put it in project state @@ -174,7 +174,7 @@ public async Task OnDocumentClosedAsync(IPersistentStorageService persiste return true; } - public bool OnDocumentReset(Document document) + public bool OnDocumentReset(TextDocument document) { var changed = false; // can not be cancelled diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 92994854a0fca..a7cb47e034732 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -29,7 +29,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 /// /// This one follows pattern compiler has set for diagnostic analyzer. /// - internal partial class DiagnosticIncrementalAnalyzer : IIncrementalAnalyzer + internal partial class DiagnosticIncrementalAnalyzer : IIncrementalAnalyzer2 { private readonly int _correlationId; private readonly DiagnosticAnalyzerTelemetry _telemetry; @@ -187,7 +187,7 @@ private void RaiseDiagnosticsRemoved( } private void RaiseDiagnosticsCreated( - Document document, StateSet stateSet, AnalysisKind kind, ImmutableArray items, Action raiseEvents) + TextDocument document, StateSet stateSet, AnalysisKind kind, ImmutableArray items, Action raiseEvents) { Contract.ThrowIfFalse(document.Project.Solution.Workspace == Workspace); @@ -238,16 +238,16 @@ public void LogAnalyzerCountSummary() internal IEnumerable GetAnalyzersTestOnly(Project project) => _stateManager.GetOrCreateStateSets(project).Select(s => s.Analyzer); - private static string GetDocumentLogMessage(string title, Document document, DiagnosticAnalyzer analyzer) + private static string GetDocumentLogMessage(string title, TextDocument document, DiagnosticAnalyzer analyzer) => $"{title}: ({document.Id}, {document.Project.Id}), ({analyzer})"; private static string GetProjectLogMessage(Project project, IEnumerable stateSets) => $"project: ({project.Id}), ({string.Join(Environment.NewLine, stateSets.Select(s => s.Analyzer.ToString()))})"; - private static string GetResetLogMessage(Document document) + private static string GetResetLogMessage(TextDocument document) => $"document close/reset: ({document.FilePath ?? document.Name})"; - private static string GetOpenLogMessage(Document document) + private static string GetOpenLogMessage(TextDocument document) => $"document open: ({document.FilePath ?? document.Name})"; private static string GetRemoveLogMessage(DocumentId id) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index cb6b5f0be52a2..f104babdf67e5 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -32,7 +32,10 @@ public Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, Can public Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken) => AnalyzeDocumentForKindAsync(document, AnalysisKind.Semantic, cancellationToken); - private async Task AnalyzeDocumentForKindAsync(Document document, AnalysisKind kind, CancellationToken cancellationToken) + public Task AnalyzeNonSourceDocumentAsync(TextDocument textDocument, InvocationReasons reasons, CancellationToken cancellationToken) + => AnalyzeDocumentForKindAsync(textDocument, AnalysisKind.Syntax, cancellationToken); + + private async Task AnalyzeDocumentForKindAsync(TextDocument document, AnalysisKind kind, CancellationToken cancellationToken) { try { @@ -133,7 +136,13 @@ private async Task AnalyzeProjectAsync(Project project, bool forceAnalyzerRun, C } } - public async Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) + public Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) + => TextDocumentOpenAsync(document, cancellationToken); + + public Task NonSourceDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken) + => TextDocumentOpenAsync(document, cancellationToken); + + private async Task TextDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Diagnostics_DocumentOpen, GetOpenLogMessage, document, cancellationToken)) { @@ -145,7 +154,13 @@ public async Task DocumentOpenAsync(Document document, CancellationToken cancell } } - public async Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) + public Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) + => TextDocumentCloseAsync(document, cancellationToken); + + public Task NonSourceDocumentCloseAsync(TextDocument document, CancellationToken cancellationToken) + => TextDocumentCloseAsync(document, cancellationToken); + + private async Task TextDocumentCloseAsync(TextDocument document, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Diagnostics_DocumentClose, GetResetLogMessage, document, cancellationToken)) { @@ -159,6 +174,12 @@ public async Task DocumentCloseAsync(Document document, CancellationToken cancel } public Task DocumentResetAsync(Document document, CancellationToken cancellationToken) + => TextDocumentResetAsync(document, cancellationToken); + + public Task NonSourceDocumentResetAsync(TextDocument document, CancellationToken cancellationToken) + => TextDocumentResetAsync(document, cancellationToken); + + private Task TextDocumentResetAsync(TextDocument document, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Diagnostics_DocumentReset, GetResetLogMessage, document, cancellationToken)) { @@ -173,7 +194,7 @@ public Task DocumentResetAsync(Document document, CancellationToken cancellation return Task.CompletedTask; } - private void RaiseDiagnosticsRemovedIfRequiredForClosedOrResetDocument(Document document, IEnumerable stateSets, bool documentHadDiagnostics) + private void RaiseDiagnosticsRemovedIfRequiredForClosedOrResetDocument(TextDocument document, IEnumerable stateSets, bool documentHadDiagnostics) { // if there was no diagnostic reported for this document OR Full solution analysis is enabled, nothing to clean up if (!documentHadDiagnostics || @@ -266,7 +287,7 @@ public Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancel return Task.CompletedTask; } - private static bool AnalysisEnabled(Document document) + private static bool AnalysisEnabled(TextDocument document) { if (document.Services.GetService()?.DiagnosticsLspClientName != null) { @@ -407,17 +428,17 @@ private void RaiseProjectDiagnosticsIfNeeded( }); } - private void RaiseDocumentDiagnosticsIfNeeded(Document document, StateSet stateSet, AnalysisKind kind, ImmutableArray items) + private void RaiseDocumentDiagnosticsIfNeeded(TextDocument document, StateSet stateSet, AnalysisKind kind, ImmutableArray items) => RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, ImmutableArray.Empty, items); private void RaiseDocumentDiagnosticsIfNeeded( - Document document, StateSet stateSet, AnalysisKind kind, ImmutableArray oldItems, ImmutableArray newItems) + TextDocument document, StateSet stateSet, AnalysisKind kind, ImmutableArray oldItems, ImmutableArray newItems) { RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, oldItems, newItems, AnalyzerService.RaiseDiagnosticsUpdated, forceUpdate: false); } private void RaiseDocumentDiagnosticsIfNeeded( - Document document, StateSet stateSet, AnalysisKind kind, + TextDocument document, StateSet stateSet, AnalysisKind kind, DiagnosticAnalysisResult oldResult, DiagnosticAnalysisResult newResult, Action raiseEvents) { @@ -438,7 +459,7 @@ private void RaiseDocumentDiagnosticsIfNeeded( } private void RaiseDocumentDiagnosticsIfNeeded( - Document document, StateSet stateSet, AnalysisKind kind, + TextDocument document, StateSet stateSet, AnalysisKind kind, ImmutableArray oldItems, ImmutableArray newItems, Action raiseEvents, bool forceUpdate) @@ -458,7 +479,7 @@ private void RaiseProjectDiagnosticsCreated(Project project, StateSet stateSet, foreach (var documentId in newAnalysisResult.DocumentIds) { - var document = project.GetDocument(documentId); + var document = project.GetTextDocument(documentId); if (document == null) { // it can happen with build synchronization since, in build case, @@ -505,7 +526,7 @@ private void RaiseProjectDiagnosticsRemoved(StateSet stateSet, ProjectId project RaiseDiagnosticsRemoved(projectId, solution: null, stateSet, raiseEvents); } - private async Task ReportAnalyzerPerformanceAsync(Document document, CompilationWithAnalyzers? compilation, CancellationToken cancellationToken) + private async Task ReportAnalyzerPerformanceAsync(TextDocument document, CompilationWithAnalyzers? compilation, CancellationToken cancellationToken) { try { diff --git a/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs b/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs index 5c4aef52807d2..a8cf77d146f8c 100644 --- a/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/SolutionCrawler/AggregateIncrementalAnalyzer.cs @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.SolutionCrawler { - internal class AggregateIncrementalAnalyzer : IIncrementalAnalyzer + internal class AggregateIncrementalAnalyzer : IIncrementalAnalyzer2 { public readonly ImmutableDictionary> Analyzers; @@ -126,5 +126,41 @@ public async Task RemoveProjectAsync(ProjectId projectId, CancellationToken canc } } } + + public async Task NonSourceDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken) + { + if (TryGetAnalyzer(document.Project, out var analyzer) && + analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentOpenAsync(document, cancellationToken).ConfigureAwait(false); + } + } + + public async Task NonSourceDocumentCloseAsync(TextDocument document, CancellationToken cancellationToken) + { + if (TryGetAnalyzer(document.Project, out var analyzer) && + analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentCloseAsync(document, cancellationToken).ConfigureAwait(false); + } + } + + public async Task NonSourceDocumentResetAsync(TextDocument document, CancellationToken cancellationToken) + { + if (TryGetAnalyzer(document.Project, out var analyzer) && + analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentResetAsync(document, cancellationToken).ConfigureAwait(false); + } + } + + public async Task AnalyzeNonSourceDocumentAsync(TextDocument document, InvocationReasons reasons, CancellationToken cancellationToken) + { + if (TryGetAnalyzer(document.Project, out var analyzer) && + analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.AnalyzeNonSourceDocumentAsync(document, reasons, cancellationToken).ConfigureAwait(false); + } + } } } diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs index 54910f9581b5a..84217fe489888 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs @@ -237,13 +237,19 @@ private void ReportPendingWorkItemCount() } private async Task ProcessDocumentAnalyzersAsync( - Document document, ImmutableArray analyzers, WorkItem workItem, CancellationToken cancellationToken) + TextDocument textDocument, ImmutableArray analyzers, WorkItem workItem, CancellationToken cancellationToken) { // process all analyzers for each categories in this order - syntax, body, document var reasons = workItem.InvocationReasons; if (workItem.MustRefresh || reasons.Contains(PredefinedInvocationReasons.SyntaxChanged)) { - await RunAnalyzersAsync(analyzers, document, workItem, (a, d, c) => a.AnalyzeSyntaxAsync(d, reasons, c), cancellationToken).ConfigureAwait(false); + await RunAnalyzersAsync(analyzers, textDocument, workItem, (a, d, c) => AnalyzeSyntaxAsync(a, d, reasons, c), cancellationToken).ConfigureAwait(false); + } + + if (!(textDocument is Document document)) + { + // Semantic analysis is not supported for non-source documents. + return; } if (workItem.MustRefresh || reasons.Contains(PredefinedInvocationReasons.SemanticChanged)) @@ -255,6 +261,20 @@ private async Task ProcessDocumentAnalyzersAsync( // if we don't need to re-analyze whole body, see whether we need to at least re-analyze one method. await RunBodyAnalyzersAsync(analyzers, workItem, document, cancellationToken).ConfigureAwait(false); } + + return; + + static async Task AnalyzeSyntaxAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, InvocationReasons reasons, CancellationToken cancellationToken) + { + if (textDocument is Document document) + { + await analyzer.AnalyzeSyntaxAsync(document, reasons, cancellationToken).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.AnalyzeNonSourceDocumentAsync(textDocument, reasons, cancellationToken).ConfigureAwait(false); + } + } } private async Task RunAnalyzersAsync( diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs index 5844f27fa6fbe..5ecee271076fe 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs @@ -334,7 +334,7 @@ private async Task ProcessDocumentAsync(ImmutableArray ana { using (Logger.LogBlock(FunctionId.WorkCoordinator_ProcessDocumentAsync, w => w.ToString(), workItem, cancellationToken)) { - var document = solution.GetDocument(documentId); + var document = solution.GetTextDocument(documentId); if (document != null) { @@ -389,7 +389,7 @@ private async Task ProcessDocumentAsync(ImmutableArray ana } } - private async Task ProcessOpenDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, Document document, bool isOpen, CancellationToken cancellationToken) + private async Task ProcessOpenDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, TextDocument document, bool isOpen, CancellationToken cancellationToken) { if (!isOpen || !workItem.InvocationReasons.Contains(PredefinedInvocationReasons.DocumentOpened)) { @@ -398,10 +398,23 @@ private async Task ProcessOpenDocumentIfNeededAsync(ImmutableArray a.DocumentOpenAsync(d, c), cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(analyzers, document, workItem, DocumentOpenAsync, cancellationToken).ConfigureAwait(false); + return; + + static async Task DocumentOpenAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, CancellationToken cancellationToken) + { + if (textDocument is Document document) + { + await analyzer.DocumentOpenAsync(document, cancellationToken).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentOpenAsync(textDocument, cancellationToken).ConfigureAwait(false); + } + } } - private async Task ProcessCloseDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, Document document, bool isOpen, CancellationToken cancellationToken) + private async Task ProcessCloseDocumentIfNeededAsync(ImmutableArray analyzers, WorkItem workItem, TextDocument document, bool isOpen, CancellationToken cancellationToken) { if (isOpen || !workItem.InvocationReasons.Contains(PredefinedInvocationReasons.DocumentClosed)) { @@ -410,10 +423,23 @@ private async Task ProcessCloseDocumentIfNeededAsync(ImmutableArray a.DocumentCloseAsync(d, c), cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(analyzers, document, workItem, DocumentCloseAsync, cancellationToken).ConfigureAwait(false); + return; + + static async Task DocumentCloseAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, CancellationToken cancellationToken) + { + if (textDocument is Document document) + { + await analyzer.DocumentCloseAsync(document, cancellationToken).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentCloseAsync(textDocument, cancellationToken).ConfigureAwait(false); + } + } } - private async Task ProcessReanalyzeDocumentAsync(WorkItem workItem, Document document, CancellationToken cancellationToken) + private async Task ProcessReanalyzeDocumentAsync(WorkItem workItem, TextDocument document, CancellationToken cancellationToken) { try { @@ -421,7 +447,7 @@ private async Task ProcessReanalyzeDocumentAsync(WorkItem workItem, Document doc Debug.Assert(!workItem.InvocationReasons.Contains(PredefinedInvocationReasons.Reanalyze) || workItem.SpecificAnalyzers.Count > 0); #endif - // no-reanalyze request or we already have a request to re-analyze every thing + // No-reanalyze request or we already have a request to re-analyze every thing if (workItem.MustRefresh || !workItem.InvocationReasons.Contains(PredefinedInvocationReasons.Reanalyze)) { return; @@ -429,25 +455,53 @@ private async Task ProcessReanalyzeDocumentAsync(WorkItem workItem, Document doc // First reset the document state in analyzers. var reanalyzers = workItem.SpecificAnalyzers.ToImmutableArray(); - await Processor.RunAnalyzersAsync(reanalyzers, document, workItem, (a, d, c) => a.DocumentResetAsync(d, c), cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(reanalyzers, document, workItem, DocumentResetAsync, cancellationToken).ConfigureAwait(false); - // no request to re-run syntax change analysis. run it here + // No request to re-run syntax change analysis. run it here var reasons = workItem.InvocationReasons; if (!reasons.Contains(PredefinedInvocationReasons.SyntaxChanged)) { - await Processor.RunAnalyzersAsync(reanalyzers, document, workItem, (a, d, c) => a.AnalyzeSyntaxAsync(d, reasons, c), cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(reanalyzers, document, workItem, (a, d, c) => AnalyzeSyntaxAsync(a, d, reasons, c), cancellationToken).ConfigureAwait(false); } - // no request to re-run semantic change analysis. run it here - if (!workItem.InvocationReasons.Contains(PredefinedInvocationReasons.SemanticChanged)) + // No request to re-run semantic change analysis. run it here + // Note: Semantic analysis is not supported for non-source documents. + if (document is Document sourceDocument && + !workItem.InvocationReasons.Contains(PredefinedInvocationReasons.SemanticChanged)) { - await Processor.RunAnalyzersAsync(reanalyzers, document, workItem, (a, d, c) => a.AnalyzeDocumentAsync(d, null, reasons, c), cancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync(reanalyzers, sourceDocument, workItem, (a, d, c) => a.AnalyzeDocumentAsync(d, null, reasons, c), cancellationToken).ConfigureAwait(false); } } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } + + return; + + static async Task DocumentResetAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, CancellationToken cancellationToken) + { + if (textDocument is Document document) + { + await analyzer.DocumentResetAsync(document, cancellationToken).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.NonSourceDocumentResetAsync(textDocument, cancellationToken).ConfigureAwait(false); + } + } + + static async Task AnalyzeSyntaxAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument, InvocationReasons reasons, CancellationToken cancellationToken) + { + if (textDocument is Document document) + { + await analyzer.AnalyzeSyntaxAsync((Document)document, reasons, cancellationToken).ConfigureAwait(false); + } + else if (analyzer is IIncrementalAnalyzer2 analyzer2) + { + await analyzer2.AnalyzeNonSourceDocumentAsync(textDocument, reasons, cancellationToken).ConfigureAwait(false); + } + } } private Task RemoveDocumentAsync(DocumentId documentId, CancellationToken cancellationToken) diff --git a/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs b/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs index da4802867cc3a..42e1b857bd6d2 100644 --- a/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs +++ b/src/Test/Utilities/Portable/Diagnostics/CommonDiagnosticAnalyzers.cs @@ -2146,7 +2146,7 @@ private void OnOperationBlockStart(OperationBlockStartAnalysisContext context) } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] - public sealed class AdditionalFileAnalyzer : DiagnosticAnalyzer + public class AdditionalFileAnalyzer : DiagnosticAnalyzer { private readonly bool _registerFromInitialize; private readonly TextSpan _diagnosticSpan; diff --git a/src/Tools/AnalyzerRunner/DiagnosticAnalyzerRunner.cs b/src/Tools/AnalyzerRunner/DiagnosticAnalyzerRunner.cs index 20ff953ba976e..817b030f828f3 100644 --- a/src/Tools/AnalyzerRunner/DiagnosticAnalyzerRunner.cs +++ b/src/Tools/AnalyzerRunner/DiagnosticAnalyzerRunner.cs @@ -464,6 +464,7 @@ private static void WriteTelemetry(string analyzerName, AnalyzerTelemetryInfo te WriteLine($"Symbol End Actions: {telemetry.SymbolEndActionsCount}", ConsoleColor.White); WriteLine($"Syntax Node Actions: {telemetry.SyntaxNodeActionsCount}", ConsoleColor.White); WriteLine($"Syntax Tree Actions: {telemetry.SyntaxTreeActionsCount}", ConsoleColor.White); + WriteLine($"Additional File Actions: {telemetry.AdditionalFileActionsCount}", ConsoleColor.White); WriteLine($"Suppression Actions: {telemetry.SuppressionActionsCount}", ConsoleColor.White); } diff --git a/src/Tools/AnalyzerRunner/Extensions.cs b/src/Tools/AnalyzerRunner/Extensions.cs index 80cf66d64818a..d4156887be9b3 100644 --- a/src/Tools/AnalyzerRunner/Extensions.cs +++ b/src/Tools/AnalyzerRunner/Extensions.cs @@ -26,6 +26,7 @@ internal static void Add(this AnalyzerTelemetryInfo analyzerTelemetryInfo, Analy analyzerTelemetryInfo.SymbolEndActionsCount += addendum.SymbolEndActionsCount; analyzerTelemetryInfo.SyntaxNodeActionsCount += addendum.SyntaxNodeActionsCount; analyzerTelemetryInfo.SyntaxTreeActionsCount += addendum.SyntaxTreeActionsCount; + analyzerTelemetryInfo.AdditionalFileActionsCount += addendum.AdditionalFileActionsCount; analyzerTelemetryInfo.SuppressionActionsCount += addendum.SuppressionActionsCount; } } diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs index ebed68d65ba84..c21cf402f684e 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticAnalysisResult.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Workspaces.Diagnostics @@ -318,7 +319,7 @@ private static void VerifyDocumentMap(Project project, ImmutableDictionary diagnostics) { - // this is for diagnostic producer that doesnt use compiler based DiagnosticAnalyzer such as TypeScript. AddExternalDiagnostics(ref _lazySyntaxLocals, documentId, diagnostics); } public void AddExternalSemanticDiagnostics(DocumentId documentId, IEnumerable diagnostics) { // this is for diagnostic producer that doesnt use compiler based DiagnosticAnalyzer such as TypeScript. + Contract.ThrowIfTrue(Project.SupportsCompilation); + AddExternalDiagnostics(ref _lazySemanticLocals, documentId, diagnostics); } private void AddExternalDiagnostics( ref Dictionary>? lazyLocals, DocumentId documentId, IEnumerable diagnostics) { - Contract.ThrowIfTrue(Project.SupportsCompilation); - foreach (var diagnostic in diagnostics) { // REVIEW: what is our plan for additional locations? @@ -73,16 +73,16 @@ private void AddExternalDiagnostics( { case LocationKind.ExternalFile: { - var diagnosticDocumentId = GetExternalDocumentId(Project, diagnostic); + var diagnosticDocumentId = Project.GetDocumentForExternalLocation(diagnostic.Location); if (documentId == diagnosticDocumentId) { // local diagnostics to a file - AddDocumentDiagnostic(ref lazyLocals, Project.GetDocument(diagnosticDocumentId), diagnostic); + AddDocumentDiagnostic(ref lazyLocals, Project.GetTextDocument(diagnosticDocumentId), diagnostic); } else if (diagnosticDocumentId != null) { // non local diagnostics to a file - AddDocumentDiagnostic(ref _lazyNonLocals, Project.GetDocument(diagnosticDocumentId), diagnostic); + AddDocumentDiagnostic(ref _lazyNonLocals, Project.GetTextDocument(diagnosticDocumentId), diagnostic); } else { @@ -109,7 +109,7 @@ private void AddExternalDiagnostics( } } - private void AddDocumentDiagnostic(ref Dictionary>? map, Document? document, Diagnostic diagnostic) + private void AddDocumentDiagnostic(ref Dictionary>? map, TextDocument? document, Diagnostic diagnostic) { if (document is null || !document.SupportsDiagnostics()) { @@ -191,14 +191,6 @@ private void AddDiagnostics( } } - private static DocumentId GetExternalDocumentId(Project project, Diagnostic diagnostic) - { - var projectId = project.Id; - var lineSpan = diagnostic.Location.GetLineSpan(); - - return project.Solution.GetDocumentIdsWithFilePath(lineSpan.Path).FirstOrDefault(id => id.ProjectId == projectId); - } - private static ImmutableDictionary> Convert(Dictionary>? map) { return map == null ? diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs index 14ca51dbd6ed1..934f8e10f3cdd 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs @@ -301,7 +301,7 @@ private static void SwapIfNeeded(ref LinePosition startLinePosition, ref LinePos } } - private static DiagnosticDataLocation? CreateLocation(Document? document, Location location) + private static DiagnosticDataLocation? CreateLocation(TextDocument? document, Location location) { if (document == null) { @@ -337,7 +337,7 @@ public static DiagnosticData Create(Diagnostic diagnostic, Project project) return Create(diagnostic, project.Id, project.Language, project.Solution.Options, location: null, additionalLocations: null, additionalProperties: null); } - public static DiagnosticData Create(Diagnostic diagnostic, Document document) + public static DiagnosticData Create(Diagnostic diagnostic, TextDocument document) { var project = document.Project; var location = CreateLocation(document, diagnostic.Location); @@ -403,9 +403,9 @@ private static DiagnosticData Create( isSuppressed: diagnostic.IsSuppressed); } - private static ImmutableDictionary? GetAdditionalProperties(Document document, Diagnostic diagnostic) + private static ImmutableDictionary? GetAdditionalProperties(TextDocument document, Diagnostic diagnostic) { - var service = document.GetLanguageService(); + var service = document.Project.GetLanguageService(); return service?.GetAdditionalProperties(diagnostic); } @@ -465,7 +465,7 @@ private static DiagnosticSeverity GetEffectiveSeverity(ReportDiagnostic effectiv } } - private static void GetLocationInfo(Document document, Location location, out TextSpan sourceSpan, out FileLinePositionSpan originalLineInfo, out FileLinePositionSpan mappedLineInfo) + private static void GetLocationInfo(TextDocument document, Location location, out TextSpan sourceSpan, out FileLinePositionSpan originalLineInfo, out FileLinePositionSpan mappedLineInfo) { var diagnosticSpanMappingService = document.Project.Solution.Workspace.Services.GetService(); if (diagnosticSpanMappingService != null) diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs index 5741cee5afee8..9e657d9263f0b 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs @@ -39,9 +39,9 @@ public DiagnosticDataSerializer(VersionStamp analyzerVersion, VersionStamp versi Version = version; } - public async Task SerializeAsync(IPersistentStorageService persistentService, Project project, Document? document, string key, ImmutableArray items, CancellationToken cancellationToken) + public async Task SerializeAsync(IPersistentStorageService persistentService, Project project, TextDocument? textDocument, string key, ImmutableArray items, CancellationToken cancellationToken) { - Contract.ThrowIfFalse(document == null || document.Project == project); + Contract.ThrowIfFalse(textDocument == null || textDocument.Project == project); using var stream = SerializableBytes.CreateWritableStream(); @@ -54,21 +54,28 @@ public async Task SerializeAsync(IPersistentStorageService persistentServi stream.Position = 0; - var writeTask = (document != null) ? - storage.WriteStreamAsync(document, key, stream, cancellationToken) : + var writeTask = (textDocument != null) ? + textDocument is Document document ? + storage.WriteStreamAsync(document, key, stream, cancellationToken) : + storage.WriteStreamAsync(GetSerializationKeyForNonSourceDocument(textDocument, key), stream, cancellationToken) : storage.WriteStreamAsync(project, key, stream, cancellationToken); return await writeTask.ConfigureAwait(false); } - public async ValueTask> DeserializeAsync(IPersistentStorageService persistentService, Project project, Document? document, string key, CancellationToken cancellationToken) + private static string GetSerializationKeyForNonSourceDocument(TextDocument document, string key) + => document.Id + ";" + key; + + public async ValueTask> DeserializeAsync(IPersistentStorageService persistentService, Project project, TextDocument? textDocument, string key, CancellationToken cancellationToken) { - Contract.ThrowIfFalse(document == null || document.Project == project); + Contract.ThrowIfFalse(textDocument == null || textDocument.Project == project); using var storage = persistentService.GetStorage(project.Solution); - var readTask = (document != null) ? - storage.ReadStreamAsync(document, key, cancellationToken) : + var readTask = (textDocument != null) ? + textDocument is Document document ? + storage.ReadStreamAsync(document, key, cancellationToken) : + storage.ReadStreamAsync(GetSerializationKeyForNonSourceDocument(textDocument, key), cancellationToken) : storage.ReadStreamAsync(project, key, cancellationToken); using var stream = await readTask.ConfigureAwait(false); @@ -79,7 +86,7 @@ public async ValueTask> DeserializeAsync(IPersist return default; } - return ReadDiagnosticData(reader, project, document, cancellationToken); + return ReadDiagnosticData(reader, project, textDocument, cancellationToken); } public void WriteDiagnosticData(ObjectWriter writer, ImmutableArray items, CancellationToken cancellationToken) @@ -176,7 +183,7 @@ private static void WriteLocation(ObjectWriter writer, DiagnosticDataLocation? i writer.WriteInt32(item.MappedEndColumn); } - public ImmutableArray ReadDiagnosticData(ObjectReader reader, Project project, Document? document, CancellationToken cancellationToken) + public ImmutableArray ReadDiagnosticData(ObjectReader reader, Project project, TextDocument? document, CancellationToken cancellationToken) { try { @@ -207,7 +214,7 @@ public ImmutableArray ReadDiagnosticData(ObjectReader reader, Pr } } - private static ImmutableArray ReadDiagnosticDataArray(ObjectReader reader, Project project, Document? document, CancellationToken cancellationToken) + private static ImmutableArray ReadDiagnosticDataArray(ObjectReader reader, Project project, TextDocument? document, CancellationToken cancellationToken) { var count = reader.ReadInt32(); if (count == 0) @@ -272,7 +279,7 @@ private static ImmutableArray ReadDiagnosticDataArray(ObjectRead return builder.ToImmutableAndFree(); } - private static DiagnosticDataLocation? ReadLocation(Project project, ObjectReader reader, Document? document) + private static DiagnosticDataLocation? ReadLocation(Project project, ObjectReader reader, TextDocument? document) { var exists = reader.ReadBoolean(); if (!exists) diff --git a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs index 43f7a8a782ce6..0cde7b6e4edfb 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs @@ -14,6 +14,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Workspaces.Diagnostics; using Roslyn.Utilities; @@ -174,6 +175,24 @@ public static ImmutableDictionary IsForkedProjectWithSemanticChangesAsync(this Proj internal static Project WithSolutionOptions(this Project project, OptionSet options) => project.Solution.WithOptions(options).GetProject(project.Id)!; + + public static TextDocument? GetTextDocument(this Project project, DocumentId? documentId) + => project.Solution.GetTextDocument(documentId); + + internal static DocumentId? GetDocumentForExternalLocation(this Project project, Location location) + { + Debug.Assert(location.Kind == LocationKind.ExternalFile); + return project.GetDocumentIdWithFilePath(location.GetLineSpan().Path); + } + + internal static DocumentId? GetDocumentForFile(this Project project, AdditionalText additionalText) + => project.GetDocumentIdWithFilePath(additionalText.Path); + + private static DocumentId? GetDocumentIdWithFilePath(this Project project, string filePath) + => project.Solution.GetDocumentIdsWithFilePath(filePath).FirstOrDefault(id => id.ProjectId == project.Id); } } diff --git a/src/Workspaces/Core/Portable/SolutionCrawler/IIncrementalAnalyzer2.cs b/src/Workspaces/Core/Portable/SolutionCrawler/IIncrementalAnalyzer2.cs new file mode 100644 index 0000000000000..0423fce9120da --- /dev/null +++ b/src/Workspaces/Core/Portable/SolutionCrawler/IIncrementalAnalyzer2.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.SolutionCrawler +{ + // CONSIDER: We can merge IIncrementalAnalyzer2 with IIncrementalAnalyzer once all of our + // IVT partners that use IIncrementalAnalyzer have migrated to ExternalAccess layer. + internal interface IIncrementalAnalyzer2 : IIncrementalAnalyzer + { + Task NonSourceDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken); + Task NonSourceDocumentCloseAsync(TextDocument document, CancellationToken cancellationToken); + + /// + /// Resets all the document state cached by the analyzer. + /// + Task NonSourceDocumentResetAsync(TextDocument document, CancellationToken cancellationToken); + + Task AnalyzeNonSourceDocumentAsync(TextDocument document, InvocationReasons reasons, CancellationToken cancellationToken); + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs index e4a5b9310dfe4..bae54bfa04efd 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/DocumentExtensions.cs @@ -58,7 +58,7 @@ public static async Task GetRequiredSyntaxRootAsync(this Document do return root ?? throw new InvalidOperationException(string.Format(WorkspaceExtensionsResources.SyntaxTree_is_required_to_accomplish_the_task_but_is_not_supported_by_document_0, document.Name)); } - public static bool IsOpen(this Document document) + public static bool IsOpen(this TextDocument document) { var workspace = document.Project.Solution.Workspace as Workspace; return workspace != null && workspace.IsDocumentOpen(document.Id);