diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ActiveFileState.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ActiveFileState.cs new file mode 100644 index 0000000000000000000000000000000000000000..c8ef7646ab63821f0b52e1bb6c67a5e8bcc2eec1 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ActiveFileState.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer + { + // TODO: implement active file state + // this should hold onto local syntax/semantic diagnostics for active file in memory. + // this should also hold onto CompilationWithAnalyzer last time used. + // this should use syntax/semantic version for its version + private class ActiveFileState + { + + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs new file mode 100644 index 0000000000000000000000000000000000000000..f98078b30d726de51553037236e11312b83b6a32 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs @@ -0,0 +1,26 @@ +// 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.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 +{ + internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer + { + // TODO: implement project state + // this should hold onto information similar to CompilerDiagnsoticExecutor.AnalysisResult + // this should use dependant project version as its version + // this should only cache opened file diagnostics in memory, and all diagnostics in other place. + // we might just use temporary storage rather than peristant storage. but will see. + // now we don't update individual document incrementally. + // but some data might comes from active file state. + private class ProjectState + { + + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 2b5b3847d4df3773f304dc9366fb5f310c4b9b02..d49c3e1e1ffea41850c17695cdacc8b59bf5efa6 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -5,10 +5,13 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Options; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { + // TODO: implement correct events and etc. internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { private readonly int _correlationId; @@ -24,6 +27,19 @@ internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncremental _correlationId = correlationId; } + private static bool AnalysisEnabled(Document document) + { + // change it to check active file (or visible files), not open files if active file tracking is enabled. + // otherwise, use open file. + return document.IsOpen(); + } + + private bool FullAnalysisEnabled(Workspace workspace, string language) + { + return workspace.Options.GetOption(ServiceFeatureOnOffOptions.ClosedFileDiagnostic, language) && + workspace.Options.GetOption(RuntimeOptions.FullSolutionAnalysis); + } + private async Task> GetProjectDiagnosticsAsync(Project project, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) { if (project == null) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs index 98d65755b5284fee8500f00457a212d0d98fc999..aa378e8c3aab4dd9646e029ee7db4ce152b59669 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs @@ -8,19 +8,17 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { + // TODO: this API will be changed to 1 API that gives all diagnostics under a project + // and that will simply replace project state public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Project project, ImmutableArray diagnostics) { - // V2 engine doesn't do anything. - // it means live error always win over build errors. build errors that can't be reported by live analyzer - // are already taken cared by engine + // TODO: for now, we dont do anything. return SpecializedTasks.EmptyTask; } public override Task SynchronizeWithBuildAsync(DiagnosticAnalyzerService.BatchUpdateToken token, Document document, ImmutableArray diagnostics) { - // V2 engine doesn't do anything. - // it means live error always win over build errors. build errors that can't be reported by live analyzer - // are already taken cared by engine + // TODO: for now, we dont do anything. return SpecializedTasks.EmptyTask; } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 59705409433c067973a504beebde75d7a3a0f41c..093213a776ae327b585b31c2c0f085234d1454a2 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -8,6 +8,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { + // TODO: need to mimic what V1 does. use cache if possible, otherwise, calculate at the spot. internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { public override Task> GetSpecificCachedDiagnosticsAsync(Solution solution, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index 668e24bebba3142c67348dd876f5957cc83a892a..e18b7d8c9651f8107877d5f27ae26ecba662c2f0 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -9,6 +9,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { + // TODO: we need to do what V1 does for LB for span. internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { public override async Task TryAppendDiagnosticsForSpanAsync(Document document, TextSpan range, List result, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 2170096df1599041a7d458305260c553befb7849..b1556d808403084e34a1c679bf72f499572b6db6 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -1,60 +1,152 @@ // 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; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { + // TODO: make it to use cache internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncrementalAnalyzer { - public override Task AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken) + public async override Task AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken) { - return SpecializedTasks.EmptyTask; - } + if (!AnalysisEnabled(document)) + { + return; + } - public override Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, CancellationToken cancellationToken) - { - return SpecializedTasks.EmptyTask; - } + // TODO: make active file state to cache compilationWithAnalyzer + // REVIEW: this is really wierd that we need compilation for syntax diagnostics which basically defeat any reason + // we have syntax diagnostics. + var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken) - { - var diagnostics = await GetDiagnosticsAsync(project.Solution, project.Id, null, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); + // TODO: make it to use state manager + var analyzers = HostAnalyzerManager.CreateDiagnosticAnalyzers(document.Project); - RaiseEvents(project, diagnostics); + // Create driver that holds onto compilation and associated analyzers + // TODO: use CompilationWithAnalyzerOption instead of AnalyzerOption so that we can have exception filter and etc + var analyzerDriver = compilation.WithAnalyzers(analyzers, document.Project.AnalyzerOptions, cancellationToken); + + foreach (var analyzer in analyzers) + { + // TODO: implement perf optimization not to run analyzers that are not needed. + // REVIEW: more unnecessary allocations just to get diagnostics per analyzer + var oneAnalyzers = ImmutableArray.Create(analyzer); + + // TODO: use cache for perf optimization + var diagnostics = await analyzerDriver.GetAnalyzerSyntaxDiagnosticsAsync(tree, oneAnalyzers, cancellationToken).ConfigureAwait(false); + + // we only care about local diagnostics + var diagnosticData = GetDiagnosticData(document.Project, diagnostics).Where(d => d.DocumentId == document.Id); + + // TODO: update using right arguments + Owner.RaiseDiagnosticsUpdated( + this, DiagnosticsUpdatedArgs.DiagnosticsCreated( + ValueTuple.Create(this, "Syntax", document.Id), document.Project.Solution.Workspace, document.Project.Solution, document.Project.Id, document.Id, diagnosticData.ToImmutableArrayOrEmpty())); + } } - public override Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) + public override async Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, CancellationToken cancellationToken) { - return SpecializedTasks.EmptyTask; + if (!AnalysisEnabled(document)) + { + return; + } + + // TODO: make active file state to cache compilationWithAnalyzer + var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + // TODO: make it to use state manager + var analyzers = HostAnalyzerManager.CreateDiagnosticAnalyzers(document.Project); + + // Create driver that holds onto compilation and associated analyzers + // TODO: use CompilationWithAnalyzerOption instead of AnalyzerOption so that we can have exception filter and etc + var analyzerDriver = compilation.WithAnalyzers(analyzers, document.Project.AnalyzerOptions, cancellationToken); + + var noSpanFilter = default(TextSpan?); + foreach (var analyzer in analyzers) + { + // REVIEW: more unnecessary allocations just to get diagnostics per analyzer + var oneAnalyzers = ImmutableArray.Create(analyzer); + + // TODO: use cache for perf optimization + // REVIEW: I think we don't even need member tracking optimization + var diagnostics = await analyzerDriver.GetAnalyzerSemanticDiagnosticsAsync(model, noSpanFilter, oneAnalyzers, cancellationToken).ConfigureAwait(false); + + var diagnosticData = GetDiagnosticData(document.Project, diagnostics).Where(d => d.DocumentId == document.Id); + + // TODO: update using right arguments + Owner.RaiseDiagnosticsUpdated( + this, DiagnosticsUpdatedArgs.DiagnosticsCreated( + ValueTuple.Create(this, "Semantic", document.Id), document.Project.Solution.Workspace, document.Project.Solution, document.Project.Id, document.Id, diagnosticData.ToImmutableArrayOrEmpty())); + } } public override Task DocumentCloseAsync(Document document, CancellationToken cancellationToken) { + // TODO: at this event, if file being closed is active file (the one in ActiveFileState), we should put that data into + // ProjectState return SpecializedTasks.EmptyTask; } public override Task DocumentResetAsync(Document document, CancellationToken cancellationToken) { - return SpecializedTasks.EmptyTask; - } - - public override Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken) - { + // REVIEW: this should reset both active file and project state the document belong to. return SpecializedTasks.EmptyTask; } public override void RemoveDocument(DocumentId documentId) { + // TODO: do proper eventing + Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( + ValueTuple.Create(this, "Syntax", documentId), Workspace, null, null, null)); + + Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( + ValueTuple.Create(this, "Semantic", documentId), Workspace, null, null, null)); + Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( ValueTuple.Create(this, documentId), Workspace, null, null, null)); } + public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken) + { + if (!FullAnalysisEnabled(project.Solution.Workspace, project.Language)) + { + // TODO: check whether there is existing state, if so, raise events to remove them all. + return; + } + + // TODO: make this to use cache. + // TODO: use CompilerDiagnosticExecutor + var diagnostics = await GetDiagnosticsAsync(project.Solution, project.Id, null, includeSuppressedDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); + + // TODO: do proper event + RaiseEvents(project, diagnostics); + } + public override void RemoveProject(ProjectId projectId) { + // TODO: do proper event Owner.RaiseDiagnosticsUpdated(this, DiagnosticsUpdatedArgs.DiagnosticsRemoved( ValueTuple.Create(this, projectId), Workspace, null, null, null)); } + + public override Task DocumentOpenAsync(Document document, CancellationToken cancellationToken) + { + // Review: I think we don't need to care about it + return SpecializedTasks.EmptyTask; + } + + public override Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken) + { + // we don't use this one. + return SpecializedTasks.EmptyTask; + } } } diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index 09a94e74426cd5466d70ade7ac32ebbf964d680d..4b58f7874136b9b924814445a9745490f3126cb2 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -180,6 +180,8 @@ + +