From ce29cf072566103a4dfff4c4414874182ad641fc Mon Sep 17 00:00:00 2001 From: HeeJae Chang Date: Thu, 29 Nov 2018 13:14:46 -0800 Subject: [PATCH 1/4] refactor and clean up diagnostic engine code so that it can be re-used stand alone. code refactored is related to properly set up CompilationWithAnalyzer and compute and return diagnostics --- .../Portable/Diagnostics/AnalyzerHelper.cs | 464 ++++++++++++++++++ .../Diagnostics/DiagnosticAnalyzerService.cs | 4 +- .../Diagnostics/EngineV2/AnalysisKind.cs | 14 + ...gnosticIncrementalAnalyzer.AnalysisKind.cs | 17 - ...cIncrementalAnalyzer.CompilationManager.cs | 84 +--- .../DiagnosticIncrementalAnalyzer.Executor.cs | 332 +------------ .../EngineV2/DiagnosticIncrementalAnalyzer.cs | 26 +- ...crementalAnalyzer_GetDiagnosticsForSpan.cs | 8 +- .../Log/DiagnosticAnalyzerLogger.cs | 6 +- 9 files changed, 506 insertions(+), 449 deletions(-) create mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/AnalysisKind.cs delete mode 100644 src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisKind.cs diff --git a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs index 1ce0bd15a08f3..0e0d75ffc21a3 100644 --- a/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs +++ b/src/Features/Core/Portable/Diagnostics/AnalyzerHelper.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -11,7 +12,11 @@ using Microsoft.CodeAnalysis.Diagnostics.Log; using Microsoft.CodeAnalysis.Diagnostics.Telemetry; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics @@ -292,6 +297,465 @@ private static IEnumerable Convert(IEnumerable<(Diagnos return analyzerPerf.Select(kv => new AnalyzerPerformanceInfo(kv.analyzer.GetAnalyzerId(), DiagnosticAnalyzerLogger.AllowsTelemetry(kv.analyzer, serviceOpt), kv.timeSpan)); } + /// + /// Get diagnostics for the given analyzers for the given document. + /// + /// this is a simple API to get all diagnostics for the given document. + /// + /// the intended audience for this API is for ones that pefer simplicity over performance such as document that belong to misc project. + /// this doesn't cache nor use cache for anything. it will re-caculate new diagnostics every time for the given document. + /// it will not persist any data on disk nor use OOP to calcuate the data. + /// + /// this should never be used when performance is a big concern. for such context, use much complex API from IDiagnosticAnalyzerService + /// that provide all kinds of knobs/cache/persistency/OOP to get better perf over simplicity + /// + public static async Task> GetDiagnosticsAsync( + this IDiagnosticAnalyzerService service, Document document, IEnumerable analyzers, AnalysisKind kind, CancellationToken cancellationToken) + { + // given service must be DiagnosticAnalyzerService + var diagnosticService = (DiagnosticAnalyzerService)service; + + var analyzerDriverOpt = await diagnosticService.CreateAnalyzerDriverAsync( + document.Project, analyzers, includeSuppressedDiagnostics: false, logAggregatorOpt: null, cancellationToken).ConfigureAwait(false); + + var builder = ArrayBuilder.GetInstance(); + foreach (var analyzer in analyzers) + { + builder.AddRange(await diagnosticService.ComputeDiagnosticsAsync( + analyzerDriverOpt, document, analyzer, kind, spanOpt: null, logAggregatorOpt: null, cancellationToken).ConfigureAwait(false)); + } + + return builder.ToImmutableAndFree(); + } + + public static async Task CreateAnalyzerDriverAsync( + this DiagnosticAnalyzerService service, + Project project, + IEnumerable analyzers, + bool includeSuppressedDiagnostics, + DiagnosticLogAggregator logAggregatorOpt, + CancellationToken cancellationToken) + { + if (!project.SupportsCompilation) + { + return null; + } + + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + + // Create driver that holds onto compilation and associated analyzers + var filteredAnalyzers = analyzers.Where(a => !a.IsWorkspaceDiagnosticAnalyzer()).ToImmutableArrayOrEmpty(); + + // PERF: there is no analyzers for this compilation. + // compilationWithAnalyzer will throw if it is created with no analyzers which is perf optimization. + if (filteredAnalyzers.IsEmpty) + { + return null; + } + + Contract.ThrowIfFalse(project.SupportsCompilation); + AssertCompilation(project, compilation); + + // Create driver that holds onto compilation and associated analyzers + return compilation.WithAnalyzers(filteredAnalyzers, GetAnalyzerOptions()); + + CompilationWithAnalyzersOptions GetAnalyzerOptions() + { + // in IDE, we always set concurrentAnalysis == false otherwise, we can get into thread starvation due to + // async being used with syncronous blocking concurrency. + return new CompilationWithAnalyzersOptions( + options: new WorkspaceAnalyzerOptions(project.AnalyzerOptions, project.Solution.Options, project.Solution), + onAnalyzerException: service.GetOnAnalyzerException(project.Id, logAggregatorOpt), + analyzerExceptionFilter: GetAnalyzerExceptionFilter(), + concurrentAnalysis: false, + logAnalyzerExecutionTime: true, + reportSuppressedDiagnostics: includeSuppressedDiagnostics); + } + + Func GetAnalyzerExceptionFilter() + { + return ex => + { + if (project.Solution.Workspace.Options.GetOption(InternalDiagnosticsOptions.CrashOnAnalyzerException)) + { + // if option is on, crash the host to get crash dump. + FatalError.ReportUnlessCanceled(ex); + } + + return true; + }; + } + } + + [Conditional("DEBUG")] + private static void AssertCompilation(Project project, Compilation compilation1) + { + // given compilation must be from given project. + Contract.ThrowIfFalse(project.TryGetCompilation(out var compilation2)); + Contract.ThrowIfFalse(compilation1 == compilation2); + } + + /// + /// Return all local diagnostics (syntax, semantic) that belong to given document for the given StateSet (analyzer) by calculating them + /// + public static async Task> ComputeDiagnosticsAsync( + this DiagnosticAnalyzerService service, + CompilationWithAnalyzers analyzerDriverOpt, + Document document, + DiagnosticAnalyzer analyzer, + AnalysisKind kind, + TextSpan? spanOpt, + DiagnosticLogAggregator logAggregatorOpt, + CancellationToken cancellationToken) + { + if (analyzer is DocumentDiagnosticAnalyzer documentAnalyzer) + { + var diagnostics = await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( + service, document, documentAnalyzer, kind, analyzerDriverOpt?.Compilation, logAggregatorOpt, cancellationToken).ConfigureAwait(false); + return diagnostics.ConvertToLocalDiagnostics(document); + } + + var documentDiagnostics = await ComputeDiagnosticAnalyzerDiagnosticsAsync().ConfigureAwait(false); + + return documentDiagnostics.ConvertToLocalDiagnostics(document); + + async Task> ComputeDiagnosticAnalyzerDiagnosticsAsync() + { + // quick optimization to reduce allocations. + if (analyzerDriverOpt == null || !service.SupportAnalysisKind(analyzer, document.Project.Language, kind)) + { + LogSyntaxInfoWithoutDiagnostics(); + return ImmutableArray.Empty; + } + + if (!await AnalysisEnabled().ConfigureAwait(false)) + { + return ImmutableArray.Empty; + } + + // REVIEW: more unnecessary allocations just to get diagnostics per analyzer + var oneAnalyzers = ImmutableArray.Create(analyzer); + + switch (kind) + { + case AnalysisKind.Syntax: + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var diagnostics = await analyzerDriverOpt.GetAnalyzerSyntaxDiagnosticsAsync(tree, oneAnalyzers, cancellationToken).ConfigureAwait(false); + LogSyntaxInfo(diagnostics, tree); + + Debug.Assert(diagnostics.Count() == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, analyzerDriverOpt.Compilation).Count()); + return diagnostics.ToImmutableArrayOrEmpty(); + case AnalysisKind.Semantic: + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + diagnostics = await analyzerDriverOpt.GetAnalyzerSemanticDiagnosticsAsync(model, spanOpt, oneAnalyzers, cancellationToken).ConfigureAwait(false); + + Debug.Assert(diagnostics.Count() == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, analyzerDriverOpt.Compilation).Count()); + return diagnostics.ToImmutableArrayOrEmpty(); + default: + return Contract.FailWithReturn>("shouldn't reach here"); + } + } + + async Task AnalysisEnabled() + { + // if project is not loaded successfully then, we disable semantic errors for compiler analyzers + if (kind == AnalysisKind.Syntax || !service.IsCompilerDiagnosticAnalyzer(document.Project.Language, analyzer)) + { + return true; + } + + var enabled = await document.Project.HasSuccessfullyLoadedAsync(cancellationToken).ConfigureAwait(false); + + Logger.Log(FunctionId.Diagnostics_SemanticDiagnostic, (a, d, e) => $"{a.ToString()}, ({d.Id}, {d.Project.Id}), Enabled:{e}", analyzer, document, enabled); + + return enabled; + } + + void LogSyntaxInfo(ImmutableArray diagnostics, SyntaxTree tree) + { + if (!diagnostics.IsDefaultOrEmpty) + { + return; + } + + Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, + (d, a, t) => $"{d.Id}, {d.Project.Id}, {a.ToString()}, {t.Length}", document, analyzer, tree); + } + + void LogSyntaxInfoWithoutDiagnostics() + { + if (kind != AnalysisKind.Syntax) + { + return; + } + + Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, + (r, d, a, k) => $"Driver: {r != null}, {d.Id}, {d.Project.Id}, {a.ToString()}, {k}", analyzerDriverOpt, document, analyzer, kind); + } + } + + public static bool SupportAnalysisKind(this DiagnosticAnalyzerService service, DiagnosticAnalyzer analyzer, string language, AnalysisKind kind) + { + // compiler diagnostic analyzer always support all kinds + if (service.IsCompilerDiagnosticAnalyzer(language, analyzer)) + { + return true; + } + + switch (kind) + { + case AnalysisKind.Syntax: + return analyzer.SupportsSyntaxDiagnosticAnalysis(); + case AnalysisKind.Semantic: + return analyzer.SupportsSemanticDiagnosticAnalysis(); + default: + return Contract.FailWithReturn("shouldn't reach here"); + } + } + + public static async Task> ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( + this DiagnosticAnalyzerService service, + Document document, + DocumentDiagnosticAnalyzer analyzer, + AnalysisKind kind, + Compilation compilationOpt, + DiagnosticLogAggregator logAggregatorOpt, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + Task> analyzeAsync; + + switch (kind) + { + case AnalysisKind.Syntax: + analyzeAsync = analyzer.AnalyzeSyntaxAsync(document, cancellationToken); + break; + + case AnalysisKind.Semantic: + analyzeAsync = analyzer.AnalyzeSemanticsAsync(document, cancellationToken); + break; + + default: + throw ExceptionUtilities.UnexpectedValue(kind); + } + + var diagnostics = (await analyzeAsync.ConfigureAwait(false)).NullToEmpty(); + if (compilationOpt != null) + { + return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationOpt); + } + +#if DEBUG + // since all DocumentDiagnosticAnalyzers are from internal users, we only do debug check. also this can be expensive at runtime + // since it requires await. if we find any offender through NFW, we should be able to fix those since all those should + // from intern teams. + await VerifyDiagnosticLocationsAsync(diagnostics, document.Project, cancellationToken).ConfigureAwait(false); +#endif + + return diagnostics; + } + catch (Exception e) when (!IsCanceled(e, cancellationToken)) + { + OnAnalyzerException(service, analyzer, document.Project.Id, compilationOpt, e, logAggregatorOpt); + return ImmutableArray.Empty; + } + } + + public static async Task> ComputeProjectDiagnosticAnalyzerDiagnosticsAsync( + this DiagnosticAnalyzerService service, + Project project, + ProjectDiagnosticAnalyzer analyzer, + Compilation compilationOpt, + DiagnosticLogAggregator logAggregatorOpt, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + var diagnostics = (await analyzer.AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false)).NullToEmpty(); + + // Apply filtering from compilation options (source suppressions, ruleset, etc.) + if (compilationOpt != null) + { + diagnostics = CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationOpt).ToImmutableArrayOrEmpty(); + } + +#if DEBUG + // since all ProjectDiagnosticAnalyzers are from internal users, we only do debug check. also this can be expensive at runtime + // since it requires await. if we find any offender through NFW, we should be able to fix those since all those should + // from intern teams. + await VerifyDiagnosticLocationsAsync(diagnostics, project, cancellationToken).ConfigureAwait(false); +#endif + + return diagnostics; + } + catch (Exception e) when (!IsCanceled(e, cancellationToken)) + { + OnAnalyzerException(service, analyzer, project.Id, compilationOpt, e, logAggregatorOpt); + return ImmutableArray.Empty; + } + } + + private static bool IsCanceled(Exception ex, CancellationToken cancellationToken) + { + return (ex as OperationCanceledException)?.CancellationToken == cancellationToken; + } + + private static void OnAnalyzerException( + DiagnosticAnalyzerService service, DiagnosticAnalyzer analyzer, ProjectId projectId, Compilation compilationOpt, Exception ex, DiagnosticLogAggregator logAggregatorOpt) + { + var exceptionDiagnostic = AnalyzerHelper.CreateAnalyzerExceptionDiagnostic(analyzer, ex); + + if (compilationOpt != null) + { + exceptionDiagnostic = CompilationWithAnalyzers.GetEffectiveDiagnostics(ImmutableArray.Create(exceptionDiagnostic), compilationOpt).SingleOrDefault(); + } + + var onAnalyzerException = service.GetOnAnalyzerException(projectId, logAggregatorOpt); + onAnalyzerException?.Invoke(ex, analyzer, exceptionDiagnostic); + } + + private static async Task VerifyDiagnosticLocationsAsync(ImmutableArray diagnostics, Project project, CancellationToken cancellationToken) + { + foreach (var diagnostic in diagnostics) + { + await VerifyDiagnosticLocationAsync(diagnostic.Id, diagnostic.Location).ConfigureAwait(false); + + if (diagnostic.AdditionalLocations != null) + { + foreach (var location in diagnostic.AdditionalLocations) + { + await VerifyDiagnosticLocationAsync(diagnostic.Id, diagnostic.Location).ConfigureAwait(false); + } + } + } + + async Task VerifyDiagnosticLocationAsync(string id, Location location) + { + switch (location.Kind) + { + case LocationKind.None: + case LocationKind.MetadataFile: + case LocationKind.XmlFile: + // ignore these kinds + break; + case LocationKind.SourceFile: + { + if (project.GetDocument(location.SourceTree) == null) + { + // Disallow diagnostics with source locations outside this project. + throw new ArgumentException(string.Format(FeaturesResources.Reported_diagnostic_0_has_a_source_location_in_file_1_which_is_not_part_of_the_compilation_being_analyzed, id, location.SourceTree.FilePath), "diagnostic"); + } + + if (location.SourceSpan.End > location.SourceTree.Length) + { + // Disallow diagnostics with source locations outside this project. + throw new ArgumentException(string.Format(FeaturesResources.Reported_diagnostic_0_has_a_source_location_1_in_file_2_which_is_outside_of_the_given_file, id, location.SourceSpan, location.SourceTree.FilePath), "diagnostic"); + } + } + break; + case LocationKind.ExternalFile: + { + var filePath = location.GetLineSpan().Path; + var document = TryGetDocumentWithFilePath(filePath); + if (document == null) + { + // this is not a roslyn file. we don't care about this file. + return; + } + + // this can be potentially expensive since it will load text if it is not already loaded. + // but, this text is most likely already loaded since producer of this diagnostic (Document/ProjectDiagnosticAnalyzers) + // should have loaded it to produce the diagnostic at the first place. once loaded, it should stay in memory until + // project cache goes away. when text is already there, await should return right away. + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + if (location.SourceSpan.End > text.Length) + { + // Disallow diagnostics with locations outside this project. + throw new ArgumentException(string.Format(FeaturesResources.Reported_diagnostic_0_has_a_source_location_1_in_file_2_which_is_outside_of_the_given_file, id, location.SourceSpan, filePath), "diagnostic"); + } + } + break; + default: + throw ExceptionUtilities.Unreachable; + } + } + + Document TryGetDocumentWithFilePath(string path) + { + foreach (var documentId in project.Solution.GetDocumentIdsWithFilePath(path)) + { + if (documentId.ProjectId == project.Id) + { + return project.GetDocument(documentId); + } + } + + return null; + } + } + + public static IEnumerable ConvertToLocalDiagnostics(this IEnumerable diagnostics, Document targetDocument, TextSpan? span = null) + { + var project = targetDocument.Project; + + if (project.SupportsCompilation) + { + return ConvertToLocalDiagnosticsWithCompilation(); + } + + return ConvertToLocalDiagnosticsWithoutCompilation(); + + IEnumerable ConvertToLocalDiagnosticsWithoutCompilation() + { + Contract.ThrowIfTrue(project.SupportsCompilation); + + foreach (var diagnostic in diagnostics) + { + 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; + } + + yield return DiagnosticData.Create(targetDocument, diagnostic); + } + } + + IEnumerable ConvertToLocalDiagnosticsWithCompilation() + { + Contract.ThrowIfFalse(project.SupportsCompilation); + + foreach (var diagnostic in diagnostics) + { + var document = project.GetDocument(diagnostic.Location.SourceTree); + if (document == null || document != targetDocument) + { + continue; + } + + if (span.HasValue && !span.Value.IntersectsWith(diagnostic.Location.SourceSpan)) + { + continue; + } + + yield return DiagnosticData.Create(document, diagnostic); + } + } + } + public static bool IsCompilationEndAnalyzer(this DiagnosticAnalyzer analyzer, Project project, Compilation compilation) { if (!project.SupportsCompilation) diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService.cs index 8f75d028bf70f..20ef92b1cb0bd 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService.cs @@ -262,12 +262,12 @@ public bool ContainsDiagnostics(Workspace workspace, ProjectId projectId) } // virtual for testing purposes. - internal virtual Action GetOnAnalyzerException(ProjectId projectId, DiagnosticLogAggregator diagnosticLogAggregator) + internal virtual Action GetOnAnalyzerException(ProjectId projectId, DiagnosticLogAggregator logAggregatorOpt) { return (ex, analyzer, diagnostic) => { // Log telemetry, if analyzer supports telemetry. - DiagnosticAnalyzerLogger.LogAnalyzerCrashCount(analyzer, ex, diagnosticLogAggregator, projectId); + DiagnosticAnalyzerLogger.LogAnalyzerCrashCount(analyzer, ex, logAggregatorOpt); AnalyzerHelper.OnAnalyzerException_NoTelemetryLogging(ex, analyzer, diagnostic, _hostDiagnosticUpdateSource, projectId); }; diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/AnalysisKind.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/AnalysisKind.cs new file mode 100644 index 0000000000000..f6e7bbdd7f378 --- /dev/null +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/AnalysisKind.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + /// + /// enum for each analysis kind. + /// + internal enum AnalysisKind + { + Syntax, + Semantic, + NonLocal + } +} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisKind.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisKind.cs deleted file mode 100644 index 1a90fb58d5169..0000000000000 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisKind.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 -{ - internal partial class DiagnosticIncrementalAnalyzer - { - /// - /// enum for each analysis kind. - /// - private enum AnalysisKind - { - Syntax, - Semantic, - NonLocal - } - } -} diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs index 4faeb20f9c929..2280fad9134e4 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs @@ -1,14 +1,11 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.ErrorReporting; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 @@ -70,79 +67,10 @@ public Task CreateAnalyzerDriverAsync(Project project, return CreateAnalyzerDriverAsync(project, analyzers, includeSuppressedDiagnostics, cancellationToken); } - public async Task CreateAnalyzerDriverAsync( + public Task CreateAnalyzerDriverAsync( Project project, IEnumerable analyzers, bool includeSuppressedDiagnostics, CancellationToken cancellationToken) { - if (!project.SupportsCompilation) - { - return null; - } - - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - - // Create driver that holds onto compilation and associated analyzers - return CreateAnalyzerDriver( - project, compilation, analyzers, logAnalyzerExecutionTime: true, reportSuppressedDiagnostics: includeSuppressedDiagnostics); - } - - private CompilationWithAnalyzers CreateAnalyzerDriver( - Project project, - Compilation compilation, - IEnumerable allAnalyzers, - bool logAnalyzerExecutionTime, - bool reportSuppressedDiagnostics) - { - var analyzers = allAnalyzers.Where(a => !a.IsWorkspaceDiagnosticAnalyzer()).ToImmutableArrayOrEmpty(); - - // PERF: there is no analyzers for this compilation. - // compilationWithAnalyzer will throw if it is created with no analyzers which is perf optimization. - if (analyzers.IsEmpty) - { - return null; - } - - Contract.ThrowIfFalse(project.SupportsCompilation); - AssertCompilation(project, compilation); - - var analysisOptions = GetAnalyzerOptions(project, logAnalyzerExecutionTime, reportSuppressedDiagnostics); - - // Create driver that holds onto compilation and associated analyzers - return compilation.WithAnalyzers(analyzers, analysisOptions); - } - - private CompilationWithAnalyzersOptions GetAnalyzerOptions( - Project project, - bool logAnalyzerExecutionTime, - bool reportSuppressedDiagnostics) - { - // in IDE, we always set concurrentAnalysis == false otherwise, we can get into thread starvation due to - // async being used with syncronous blocking concurrency. - return new CompilationWithAnalyzersOptions( - options: new WorkspaceAnalyzerOptions(project.AnalyzerOptions, project.Solution.Options, project.Solution), - onAnalyzerException: GetOnAnalyzerException(project.Id), - analyzerExceptionFilter: GetAnalyzerExceptionFilter(project), - concurrentAnalysis: false, - logAnalyzerExecutionTime: logAnalyzerExecutionTime, - reportSuppressedDiagnostics: reportSuppressedDiagnostics); - } - - private Func GetAnalyzerExceptionFilter(Project project) - { - return ex => - { - if (project.Solution.Workspace.Options.GetOption(InternalDiagnosticsOptions.CrashOnAnalyzerException)) - { - // if option is on, crash the host to get crash dump. - FatalError.ReportUnlessCanceled(ex); - } - - return true; - }; - } - - private Action GetOnAnalyzerException(ProjectId projectId) - { - return _owner.Owner.GetOnAnalyzerException(projectId, _owner.DiagnosticLogAggregator); + return _owner.Owner.CreateAnalyzerDriverAsync(project, analyzers, includeSuppressedDiagnostics, _owner.DiagnosticLogAggregator, cancellationToken); } private void ResetAnalyzerDriverMap() @@ -169,14 +97,6 @@ private void AssertAnalyzers(CompilationWithAnalyzers analyzerDriver, IEnumerabl Contract.ThrowIfFalse(analyzerDriver.Analyzers.SetEquals(stateSets.Select(s => s.Analyzer).Where(a => !a.IsWorkspaceDiagnosticAnalyzer()))); } - [Conditional("DEBUG")] - private void AssertCompilation(Project project, Compilation compilation1) - { - // given compilation must be from given project. - Contract.ThrowIfFalse(project.TryGetCompilation(out var compilation2)); - Contract.ThrowIfFalse(compilation1 == compilation2); - } - #region state changed public void OnActiveDocumentChanged() { diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index 9a812acab5de3..283a8b4c35c0b 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -35,18 +34,6 @@ public Executor(DiagnosticIncrementalAnalyzer owner) _diagnosticAnalyzerRunner = new InProcOrRemoteHostAnalyzerRunner(_owner.Owner, _owner.HostDiagnosticUpdateSource); } - public IEnumerable ConvertToLocalDiagnostics(Document targetDocument, IEnumerable diagnostics, TextSpan? span = null) - { - var project = targetDocument.Project; - - if (project.SupportsCompilation) - { - return ConvertToLocalDiagnosticsWithCompilation(targetDocument, diagnostics, span); - } - - return ConvertToLocalDiagnosticsWithoutCompilation(targetDocument, diagnostics, span); - } - /// /// Return all local diagnostics (syntax, semantic) that belong to given document for the given StateSet (analyzer) either from cache or by calculating them /// @@ -171,20 +158,10 @@ private async Task - /// Return all local diagnostics (syntax, semantic) that belong to given document for the given StateSet (analyzer) by calculating them - /// - public async Task> ComputeDiagnosticsAsync( - CompilationWithAnalyzers analyzerDriverOpt, Document document, DiagnosticAnalyzer analyzer, AnalysisKind kind, TextSpan? spanOpt, CancellationToken cancellationToken) + public Task> ComputeDiagnosticsAsync( + CompilationWithAnalyzers analyzerDriverOpt, Document document, DiagnosticAnalyzer analyzer, AnalysisKind kind, TextSpan? spanOpt, CancellationToken cancellationToken) { - if (analyzer is DocumentDiagnosticAnalyzer documentAnalyzer) - { - var diagnostics = await ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, documentAnalyzer, kind, analyzerDriverOpt?.Compilation, cancellationToken).ConfigureAwait(false); - return ConvertToLocalDiagnostics(document, diagnostics); - } - - var documentDiagnostics = await ComputeDiagnosticAnalyzerDiagnosticsAsync(analyzerDriverOpt, document, analyzer, kind, spanOpt, cancellationToken).ConfigureAwait(false); - return ConvertToLocalDiagnostics(document, documentDiagnostics); + return _owner.Owner.ComputeDiagnosticsAsync(analyzerDriverOpt, document, analyzer, kind, spanOpt, _owner.DiagnosticLogAggregator, cancellationToken); } /// @@ -369,133 +346,23 @@ private async Task> ComputeProjectDiagnosticAnalyzerDiagnosticsAsync( - Project project, ProjectDiagnosticAnalyzer analyzer, Compilation compilationOpt, CancellationToken cancellationToken) + private Task> ComputeProjectDiagnosticAnalyzerDiagnosticsAsync( + Project project, + ProjectDiagnosticAnalyzer analyzer, + Compilation compilationOpt, + CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - var diagnostics = (await analyzer.AnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false)).NullToEmpty(); - - // Apply filtering from compilation options (source suppressions, ruleset, etc.) - if (compilationOpt != null) - { - diagnostics = CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationOpt).ToImmutableArrayOrEmpty(); - } - -#if DEBUG - // since all ProjectDiagnosticAnalyzers are from internal users, we only do debug check. also this can be expensive at runtime - // since it requires await. if we find any offender through NFW, we should be able to fix those since all those should - // from intern teams. - await VerifyDiagnosticLocationsAsync(diagnostics, project, cancellationToken).ConfigureAwait(false); -#endif - - return diagnostics; - } - catch (Exception e) when (!IsCanceled(e, cancellationToken)) - { - OnAnalyzerException(analyzer, project.Id, compilationOpt, e); - return ImmutableArray.Empty; - } + return _owner.Owner.ComputeProjectDiagnosticAnalyzerDiagnosticsAsync(project, analyzer, compilationOpt, _owner.DiagnosticLogAggregator, cancellationToken); } - private async Task> ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( - Document document, DocumentDiagnosticAnalyzer analyzer, AnalysisKind kind, Compilation compilationOpt, CancellationToken cancellationToken) + private Task> ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync( + Document document, + DocumentDiagnosticAnalyzer analyzer, + AnalysisKind kind, + Compilation compilationOpt, + CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - Task> analyzeAsync; - - switch (kind) - { - case AnalysisKind.Syntax: - analyzeAsync = analyzer.AnalyzeSyntaxAsync(document, cancellationToken); - break; - - case AnalysisKind.Semantic: - analyzeAsync = analyzer.AnalyzeSemanticsAsync(document, cancellationToken); - break; - - default: - throw ExceptionUtilities.UnexpectedValue(kind); - } - - var diagnostics = (await analyzeAsync.ConfigureAwait(false)).NullToEmpty(); - if (compilationOpt != null) - { - return CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, compilationOpt); - } - -#if DEBUG - // since all DocumentDiagnosticAnalyzers are from internal users, we only do debug check. also this can be expensive at runtime - // since it requires await. if we find any offender through NFW, we should be able to fix those since all those should - // from intern teams. - await VerifyDiagnosticLocationsAsync(diagnostics, document.Project, cancellationToken).ConfigureAwait(false); -#endif - - return diagnostics; - } - catch (Exception e) when (!IsCanceled(e, cancellationToken)) - { - OnAnalyzerException(analyzer, document.Project.Id, compilationOpt, e); - return ImmutableArray.Empty; - } - } - - private async Task> ComputeDiagnosticAnalyzerDiagnosticsAsync( - CompilationWithAnalyzers analyzerDriverOpt, Document document, DiagnosticAnalyzer analyzer, AnalysisKind kind, TextSpan? spanOpt, CancellationToken cancellationToken) - { - // quick optimization to reduce allocations. - if (analyzerDriverOpt == null || !_owner.SupportAnalysisKind(analyzer, document.Project.Language, kind)) - { - LogSyntaxInfo(analyzerDriverOpt, document, analyzer, kind); - return ImmutableArray.Empty; - } - - if (!await AnalysisEnabled(document, analyzer, kind, cancellationToken).ConfigureAwait(false)) - { - return ImmutableArray.Empty; - } - - // REVIEW: more unnecessary allocations just to get diagnostics per analyzer - var oneAnalyzers = ImmutableArray.Create(analyzer); - - switch (kind) - { - case AnalysisKind.Syntax: - var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var diagnostics = await analyzerDriverOpt.GetAnalyzerSyntaxDiagnosticsAsync(tree, oneAnalyzers, cancellationToken).ConfigureAwait(false); - LogSyntaxInfo(document, analyzer, diagnostics, tree); - - Debug.Assert(diagnostics.Count() == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, analyzerDriverOpt.Compilation).Count()); - return diagnostics.ToImmutableArrayOrEmpty(); - case AnalysisKind.Semantic: - var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - diagnostics = await analyzerDriverOpt.GetAnalyzerSemanticDiagnosticsAsync(model, spanOpt, oneAnalyzers, cancellationToken).ConfigureAwait(false); - - Debug.Assert(diagnostics.Count() == CompilationWithAnalyzers.GetEffectiveDiagnostics(diagnostics, analyzerDriverOpt.Compilation).Count()); - return diagnostics.ToImmutableArrayOrEmpty(); - default: - return Contract.FailWithReturn>("shouldn't reach here"); - } - } - - private async Task AnalysisEnabled(Document document, DiagnosticAnalyzer analyzer, AnalysisKind kind, CancellationToken cancellationToken) - { - // if project is not loaded successfully then, we disable semantic errors for compiler analyzers - if (kind == AnalysisKind.Syntax || !_owner.HostAnalyzerManager.IsCompilerDiagnosticAnalyzer(document.Project.Language, analyzer)) - { - return true; - } - - var enabled = await document.Project.HasSuccessfullyLoadedAsync(cancellationToken).ConfigureAwait(false); - - Logger.Log(FunctionId.Diagnostics_SemanticDiagnostic, (a, d, e) => $"{a.ToString()}, ({d.Id}, {d.Project.Id}), Enabled:{e}", analyzer, document, enabled); - - return enabled; + return _owner.Owner.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(document, analyzer, kind, compilationOpt, _owner.DiagnosticLogAggregator, cancellationToken); } private void UpdateAnalyzerTelemetryData( @@ -524,49 +391,6 @@ private static bool FullAnalysisEnabled(Project project, bool forceAnalyzerRun) return true; } - private static bool IsCanceled(Exception ex, CancellationToken cancellationToken) - { - return (ex as OperationCanceledException)?.CancellationToken == cancellationToken; - } - - private void OnAnalyzerException(DiagnosticAnalyzer analyzer, ProjectId projectId, Compilation compilationOpt, Exception ex) - { - var exceptionDiagnostic = AnalyzerHelper.CreateAnalyzerExceptionDiagnostic(analyzer, ex); - - if (compilationOpt != null) - { - exceptionDiagnostic = CompilationWithAnalyzers.GetEffectiveDiagnostics(ImmutableArray.Create(exceptionDiagnostic), compilationOpt).SingleOrDefault(); - } - - var onAnalyzerException = _owner.GetOnAnalyzerException(projectId); - onAnalyzerException(ex, analyzer, exceptionDiagnostic); - } - - private IEnumerable ConvertToLocalDiagnosticsWithoutCompilation(Document targetDocument, IEnumerable diagnostics, TextSpan? span = null) - { - var project = targetDocument.Project; - Contract.ThrowIfTrue(project.SupportsCompilation); - - foreach (var diagnostic in diagnostics) - { - 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; - } - - yield return DiagnosticData.Create(targetDocument, diagnostic); - } - } - private async Task> AnalyzeAsync( CompilationWithAnalyzers analyzerDriver, Project project, bool forcedAnalysis, CancellationToken cancellationToken) { @@ -581,108 +405,6 @@ private async Task ConvertToLocalDiagnosticsWithCompilation(Document targetDocument, IEnumerable diagnostics, TextSpan? span = null) - { - var project = targetDocument.Project; - Contract.ThrowIfFalse(project.SupportsCompilation); - - foreach (var diagnostic in diagnostics) - { - var document = project.GetDocument(diagnostic.Location.SourceTree); - if (document == null || document != targetDocument) - { - continue; - } - - if (span.HasValue && !span.Value.IntersectsWith(diagnostic.Location.SourceSpan)) - { - continue; - } - - yield return DiagnosticData.Create(document, diagnostic); - } - } - - private static async Task VerifyDiagnosticLocationsAsync(ImmutableArray diagnostics, Project project, CancellationToken cancellationToken) - { - foreach (var diagnostic in diagnostics) - { - await VerifyDiagnosticLocationAsync(diagnostic.Id, diagnostic.Location, project, cancellationToken).ConfigureAwait(false); - - if (diagnostic.AdditionalLocations != null) - { - foreach (var location in diagnostic.AdditionalLocations) - { - await VerifyDiagnosticLocationAsync(diagnostic.Id, diagnostic.Location, project, cancellationToken).ConfigureAwait(false); - } - } - } - } - - private static async Task VerifyDiagnosticLocationAsync(string id, Location location, Project project, CancellationToken cancellationToken) - { - switch (location.Kind) - { - case LocationKind.None: - case LocationKind.MetadataFile: - case LocationKind.XmlFile: - // ignore these kinds - break; - case LocationKind.SourceFile: - { - if (project.GetDocument(location.SourceTree) == null) - { - // Disallow diagnostics with source locations outside this project. - throw new ArgumentException(string.Format(FeaturesResources.Reported_diagnostic_0_has_a_source_location_in_file_1_which_is_not_part_of_the_compilation_being_analyzed, id, location.SourceTree.FilePath), "diagnostic"); - } - - if (location.SourceSpan.End > location.SourceTree.Length) - { - // Disallow diagnostics with source locations outside this project. - throw new ArgumentException(string.Format(FeaturesResources.Reported_diagnostic_0_has_a_source_location_1_in_file_2_which_is_outside_of_the_given_file, id, location.SourceSpan, location.SourceTree.FilePath), "diagnostic"); - } - } - break; - case LocationKind.ExternalFile: - { - var filePath = location.GetLineSpan().Path; - var document = TryGetDocumentWithFilePath(project, filePath); - if (document == null) - { - // this is not a roslyn file. we don't care about this file. - return; - } - - // this can be potentially expensive since it will load text if it is not already loaded. - // but, this text is most likely already loaded since producer of this diagnostic (Document/ProjectDiagnosticAnalyzers) - // should have loaded it to produce the diagnostic at the first place. once loaded, it should stay in memory until - // project cache goes away. when text is already there, await should return right away. - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - if (location.SourceSpan.End > text.Length) - { - // Disallow diagnostics with locations outside this project. - throw new ArgumentException(string.Format(FeaturesResources.Reported_diagnostic_0_has_a_source_location_1_in_file_2_which_is_outside_of_the_given_file, id, location.SourceSpan, filePath), "diagnostic"); - } - } - break; - default: - throw ExceptionUtilities.Unreachable; - } - } - - private static Document TryGetDocumentWithFilePath(Project project, string path) - { - foreach (var documentId in project.Solution.GetDocumentIdsWithFilePath(path)) - { - if (documentId.ProjectId == project.Id) - { - return project.GetDocument(documentId); - } - } - - return null; - } - private static void GetLogFunctionIdAndTitle(AnalysisKind kind, out FunctionId functionId, out string title) { switch (kind) @@ -702,28 +424,6 @@ private static void GetLogFunctionIdAndTitle(AnalysisKind kind, out FunctionId f break; } } - - private static void LogSyntaxInfo(Document document, DiagnosticAnalyzer analyzer, ImmutableArray diagnostics, SyntaxTree tree) - { - if (!diagnostics.IsDefaultOrEmpty) - { - return; - } - - Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, - (d, a, t) => $"{d.Id}, {d.Project.Id}, {a.ToString()}, {t.Length}", document, analyzer, tree); - } - - private static void LogSyntaxInfo(CompilationWithAnalyzers analyzerDriverOpt, Document document, DiagnosticAnalyzer analyzer, AnalysisKind kind) - { - if (kind != AnalysisKind.Syntax) - { - return; - } - - Logger.Log(FunctionId.Diagnostics_SyntaxDiagnostic, - (r, d, a, k) => $"Driver: {r != null}, {d.Id}, {d.Project.Id}, {a.ToString()}, {k}", analyzerDriverOpt, document, analyzer, kind); - } } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 78991258fe34b..714ecbb4b5817 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics.Log; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Options; using Microsoft.CodeAnalysis.Simplification; @@ -88,25 +89,6 @@ public bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs e.Option == RuntimeOptions.FullSolutionAnalysis; } - private bool SupportAnalysisKind(DiagnosticAnalyzer analyzer, string language, AnalysisKind kind) - { - // compiler diagnostic analyzer always support all kinds - if (HostAnalyzerManager.IsCompilerDiagnosticAnalyzer(language, analyzer)) - { - return true; - } - - switch (kind) - { - case AnalysisKind.Syntax: - return analyzer.SupportsSyntaxDiagnosticAnalysis(); - case AnalysisKind.Semantic: - return analyzer.SupportsSemanticDiagnosticAnalysis(); - default: - return Contract.FailWithReturn("shouldn't reach here"); - } - } - private void OnProjectAnalyzerReferenceChanged(object sender, ProjectAnalyzerReferenceChangedEventArgs e) { if (e.Removed.Length == 0) @@ -282,12 +264,6 @@ private void ResetDiagnosticLogAggregator() DiagnosticLogAggregator = new DiagnosticLogAggregator(Owner); } - // internal for testing purposes. - internal Action GetOnAnalyzerException(ProjectId projectId) - { - return Owner.GetOnAnalyzerException(projectId, DiagnosticLogAggregator); - } - internal IEnumerable GetAnalyzersTestOnly(Project project) { return _stateManager.GetOrCreateStateSets(project).Select(s => s.Analyzer); diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index ec7442819b39a..86d8c39f9dc2c 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -180,7 +180,7 @@ private async Task> GetCompilerSyntaxDiagnosticsAsyn var root = await _document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var diagnostics = root.GetDiagnostics(); - return _owner._executor.ConvertToLocalDiagnostics(_document, diagnostics, _range); + return diagnostics.ConvertToLocalDiagnostics(_document, _range); } private async Task> GetCompilerSemanticDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) @@ -192,7 +192,7 @@ private async Task> GetCompilerSemanticDiagnosticsAs var adjustedSpan = AdjustSpan(_document, root, _range); var diagnostics = model.GetDeclarationDiagnostics(adjustedSpan, cancellationToken).Concat(model.GetMethodBodyDiagnostics(adjustedSpan, cancellationToken)); - return _owner._executor.ConvertToLocalDiagnostics(_document, diagnostics, _range); + return diagnostics.ConvertToLocalDiagnostics(_document, _range); } private Task> GetSyntaxDiagnosticsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) @@ -307,7 +307,7 @@ private async Task TryGetDocumentDiagnosticsAsync( List list, CancellationToken cancellationToken) { - if (!_owner.SupportAnalysisKind(stateSet.Analyzer, stateSet.Language, kind)) + if (!_owner.Owner.SupportAnalysisKind(stateSet.Analyzer, stateSet.Language, kind)) { return true; } @@ -400,7 +400,7 @@ private async Task TryGetProjectDiagnosticsAsync( private bool ShouldInclude(DiagnosticData diagnostic) { - return diagnostic.DocumentId == _document.Id && _range.IntersectsWith(diagnostic.TextSpan) + return diagnostic.DocumentId == _document.Id && _range.IntersectsWith(diagnostic.TextSpan) && (_includeSuppressedDiagnostics || !diagnostic.IsSuppressed) && (_diagnosticId == null || _diagnosticId == diagnostic.Id); } diff --git a/src/Features/Core/Portable/Diagnostics/Log/DiagnosticAnalyzerLogger.cs b/src/Features/Core/Portable/Diagnostics/Log/DiagnosticAnalyzerLogger.cs index 63e47274a7422..4d16777b4d502 100644 --- a/src/Features/Core/Portable/Diagnostics/Log/DiagnosticAnalyzerLogger.cs +++ b/src/Features/Core/Portable/Diagnostics/Log/DiagnosticAnalyzerLogger.cs @@ -44,9 +44,9 @@ public static void LogWorkspaceAnalyzers(ImmutableArray analy })); } - public static void LogAnalyzerCrashCount(DiagnosticAnalyzer analyzer, Exception ex, LogAggregator logAggregator, ProjectId projectId) + public static void LogAnalyzerCrashCount(DiagnosticAnalyzer analyzer, Exception ex, LogAggregator logAggregatorOpt) { - if (logAggregator == null || analyzer == null || ex == null || ex is OperationCanceledException) + if (logAggregatorOpt == null || analyzer == null || ex == null || ex is OperationCanceledException) { return; } @@ -54,7 +54,7 @@ public static void LogAnalyzerCrashCount(DiagnosticAnalyzer analyzer, Exception // TODO: once we create description manager, pass that into here. bool telemetry = DiagnosticAnalyzerLogger.AllowsTelemetry(analyzer, null); var tuple = ValueTuple.Create(telemetry, analyzer.GetType(), ex.GetType()); - logAggregator.IncreaseCount(tuple); + logAggregatorOpt.IncreaseCount(tuple); } public static void LogAnalyzerCrashCountSummary(int correlationId, LogAggregator logAggregator) From 4a571bddf7e118d8d508c43b748fccb0e68da732 Mon Sep 17 00:00:00 2001 From: HeeJae Chang Date: Thu, 29 Nov 2018 13:19:58 -0800 Subject: [PATCH 2/4] made default diagnostic analyzer service to share code with VS diagnostic analyzer service on computing diagnostics. unlike VS diagnostic analyzer, the default one doesn't persist data or calculate diagnostics per a analyzer to improve per analyzer freshes nor compute and maintain per project analyzers and etc. also, this add ability to calculate semantic diagnostics for script file. --- .../DefaultDiagnosticAnalyzerService.cs | 96 +++++++++++------- .../DefaultDiagnosticUpdateSourceTests.vb | 99 +++++++++++++++---- .../Diagnostics/DiagnosticProvider.cs | 8 +- .../InternalRuntimeDiagnosticOptions.cs | 1 + 4 files changed, 145 insertions(+), 59 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs index cdbd9ca43309e..6389cfdc8d74a 100644 --- a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Diagnostics; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Options; @@ -18,12 +18,14 @@ namespace Microsoft.CodeAnalysis.Diagnostics [ExportIncrementalAnalyzerProvider(WellKnownSolutionCrawlerAnalyzers.Diagnostic, workspaceKinds: null)] internal partial class DefaultDiagnosticAnalyzerService : IIncrementalAnalyzerProvider, IDiagnosticUpdateSource { - private const int Syntax = 1; - private const int Semantic = 2; + private readonly IDiagnosticAnalyzerService _analyzerService; [ImportingConstructor] - public DefaultDiagnosticAnalyzerService(IDiagnosticUpdateSourceRegistrationService registrationService) + public DefaultDiagnosticAnalyzerService( + IDiagnosticAnalyzerService analyzerService, + IDiagnosticUpdateSourceRegistrationService registrationService) { + _analyzerService = analyzerService; registrationService.Register(this); } @@ -34,14 +36,13 @@ public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) return null; } - return new CompilerDiagnosticAnalyzer(this, workspace); + return new DefaultDiagnosticIncrementalAnalyzer(this, workspace); } public event EventHandler DiagnosticsUpdated; - public bool SupportGetDiagnostics => - // this only support push model, pull model will be provided by DiagnosticService by caching everything this one pushed - false; + // this only support push model, pull model will be provided by DiagnosticService by caching everything this one pushed + public bool SupportGetDiagnostics => false; public ImmutableArray GetDiagnostics(Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default) { @@ -54,12 +55,12 @@ internal void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs state) this.DiagnosticsUpdated?.Invoke(this, state); } - private class CompilerDiagnosticAnalyzer : IIncrementalAnalyzer + private class DefaultDiagnosticIncrementalAnalyzer : IIncrementalAnalyzer { private readonly DefaultDiagnosticAnalyzerService _service; private readonly Workspace _workspace; - public CompilerDiagnosticAnalyzer(DefaultDiagnosticAnalyzerService service, Workspace workspace) + public DefaultDiagnosticIncrementalAnalyzer(DefaultDiagnosticAnalyzerService service, Workspace workspace) { _service = service; _workspace = workspace; @@ -68,7 +69,8 @@ public CompilerDiagnosticAnalyzer(DefaultDiagnosticAnalyzerService service, Work public bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs e) { if (e.Option == InternalRuntimeDiagnosticOptions.Syntax || - e.Option == InternalRuntimeDiagnosticOptions.Semantic) + e.Option == InternalRuntimeDiagnosticOptions.Semantic || + e.Option == InternalRuntimeDiagnosticOptions.ScriptSemantic) { return true; } @@ -78,53 +80,73 @@ public bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs public async Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, CancellationToken cancellationToken) { + Debug.Assert(document.Project.Solution.Workspace == _workspace); + // right now, there is no way to observe diagnostics for closed file. if (!_workspace.IsDocumentOpen(document.Id) || - !_workspace.Options.GetOption(InternalRuntimeDiagnosticOptions.Syntax) || - !document.SupportsSyntaxTree) + !_workspace.Options.GetOption(InternalRuntimeDiagnosticOptions.Syntax)) { return; } - var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var diagnostics = tree.GetDiagnostics(cancellationToken); - - Debug.Assert(document.Project.Solution.Workspace == _workspace); - - var diagnosticData = diagnostics == null ? ImmutableArray.Empty : diagnostics.Select(d => DiagnosticData.Create(document, d)).ToImmutableArrayOrEmpty(); - - _service.RaiseDiagnosticsUpdated( - DiagnosticsUpdatedArgs.DiagnosticsCreated(new DefaultUpdateArgsId(_workspace.Kind, Syntax, document.Id), - _workspace, document.Project.Solution, document.Project.Id, document.Id, diagnosticData)); + await AnalyzeForKind(document, AnalysisKind.Syntax, cancellationToken).ConfigureAwait(false); } public async Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken) { - // right now, there is no way to observe diagnostics for closed file. - if (!_workspace.IsDocumentOpen(document.Id) || - !_workspace.Options.GetOption(InternalRuntimeDiagnosticOptions.Semantic)) + Debug.Assert(document.Project.Solution.Workspace == _workspace); + + if (!IsSemanticAnalysisOn()) { return; } - var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var diagnostics = model.GetMethodBodyDiagnostics(span: null, cancellationToken: cancellationToken).Concat( - model.GetDeclarationDiagnostics(span: null, cancellationToken: cancellationToken)); + await AnalyzeForKind(document, AnalysisKind.Semantic, cancellationToken).ConfigureAwait(false); - Debug.Assert(document.Project.Solution.Workspace == _workspace); + bool IsSemanticAnalysisOn() + { + // right now, there is no way to observe diagnostics for closed file. + if (!_workspace.IsDocumentOpen(document.Id)) + { + return false; + } + + if (_workspace.Options.GetOption(InternalRuntimeDiagnosticOptions.Semantic)) + { + return true; + } + + return _workspace.Options.GetOption(InternalRuntimeDiagnosticOptions.ScriptSemantic) && document.SourceCodeKind == SourceCodeKind.Script; + } + } - var diagnosticData = diagnostics == null ? ImmutableArray.Empty : diagnostics.Select(d => DiagnosticData.Create(document, d)).ToImmutableArrayOrEmpty(); + private async Task AnalyzeForKind(Document document, AnalysisKind kind, CancellationToken cancellationToken) + { + var diagnosticData = await _service._analyzerService.GetDiagnosticsAsync(document, GetAnalyzers(), kind, cancellationToken).ConfigureAwait(false); _service.RaiseDiagnosticsUpdated( - DiagnosticsUpdatedArgs.DiagnosticsCreated(new DefaultUpdateArgsId(_workspace.Kind, Semantic, document.Id), - _workspace, document.Project.Solution, document.Project.Id, document.Id, diagnosticData)); + DiagnosticsUpdatedArgs.DiagnosticsCreated(new DefaultUpdateArgsId(_workspace.Kind, kind, document.Id), + _workspace, document.Project.Solution, document.Project.Id, document.Id, diagnosticData.ToImmutableArrayOrEmpty())); + + IEnumerable GetAnalyzers() + { + // C# or VB document that supports compiler + var compilerAnalyzer = _service._analyzerService.GetCompilerDiagnosticAnalyzer(document.Project.Language); + if (compilerAnalyzer != null) + { + return SpecializedCollections.SingletonEnumerable(compilerAnalyzer); + } + + // document that doesn't support compiler diagnostics such as fsharp or typescript + return _service._analyzerService.GetDiagnosticAnalyzers(document.Project); + } } public void RemoveDocument(DocumentId documentId) { // a file is removed from misc project - RaiseEmptyDiagnosticUpdated(Syntax, documentId); - RaiseEmptyDiagnosticUpdated(Semantic, documentId); + RaiseEmptyDiagnosticUpdated(AnalysisKind.Syntax, documentId); + RaiseEmptyDiagnosticUpdated(AnalysisKind.Semantic, documentId); } public Task DocumentResetAsync(Document document, CancellationToken cancellationToken) @@ -139,7 +161,7 @@ public Task DocumentCloseAsync(Document document, CancellationToken cancellation return DocumentResetAsync(document, cancellationToken); } - private void RaiseEmptyDiagnosticUpdated(int kind, DocumentId documentId) + private void RaiseEmptyDiagnosticUpdated(AnalysisKind kind, DocumentId documentId) { _service.RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs.DiagnosticsRemoved( new DefaultUpdateArgsId(_workspace.Kind, kind, documentId), _workspace, null, documentId.ProjectId, documentId)); @@ -168,7 +190,7 @@ private class DefaultUpdateArgsId : BuildToolId.Base, ISupportL { private readonly string _workspaceKind; - public DefaultUpdateArgsId(string workspaceKind, int type, DocumentId documentId) : base(type, documentId) + public DefaultUpdateArgsId(string workspaceKind, AnalysisKind kind, DocumentId documentId) : base((int)kind, documentId) { _workspaceKind = workspaceKind; } diff --git a/src/VisualStudio/Core/Test/Diagnostics/DefaultDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/DefaultDiagnosticUpdateSourceTests.vb index c04a0f3535923..b673c8e58105d 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/DefaultDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/DefaultDiagnosticUpdateSourceTests.vb @@ -26,10 +26,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics class 123 { } Using workspace = TestWorkspace.CreateCSharp(code.Value) - Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider) - Dim diagnosticService = New DiagnosticService(listenerProvider) + Dim diagnosticService = DirectCast(workspace.ExportProvider.GetExportedValue(Of IDiagnosticService), DiagnosticService) - Dim miscService = New DefaultDiagnosticAnalyzerService(diagnosticService) + Dim miscService = New DefaultDiagnosticAnalyzerService(New TestDiagnosticAnalyzerService(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()), diagnosticService) Assert.False(miscService.SupportGetDiagnostics) DiagnosticProvider.Enable(workspace, DiagnosticProvider.Options.Syntax) @@ -38,8 +37,11 @@ class 123 { } WpfTestRunner.RequireWpfFact($"This test uses {NameOf(IForegroundNotificationService)}") Dim foregroundService = workspace.GetService(Of IForegroundNotificationService)() + Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider) + Dim provider = New DiagnosticsSquiggleTaggerProvider(workspace.ExportProvider.GetExportedValue(Of IThreadingContext), diagnosticService, foregroundService, listenerProvider) Dim tagger = provider.CreateTagger(Of IErrorTag)(buffer) + Using disposable = TryCast(tagger, IDisposable) Dim analyzer = miscService.CreateIncrementalAnalyzer(workspace) Await analyzer.AnalyzeSyntaxAsync(workspace.CurrentSolution.Projects.First().Documents.First(), InvocationReasons.Empty, CancellationToken.None) @@ -69,10 +71,9 @@ class A Using workspace = TestWorkspace.CreateCSharp(code.Value) - Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider) - Dim diagnosticService = New DiagnosticService(listenerProvider) + Dim diagnosticService = DirectCast(workspace.ExportProvider.GetExportedValue(Of IDiagnosticService), DiagnosticService) - Dim miscService = New DefaultDiagnosticAnalyzerService(diagnosticService) + Dim miscService = New DefaultDiagnosticAnalyzerService(New TestDiagnosticAnalyzerService(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()), diagnosticService) Assert.False(miscService.SupportGetDiagnostics) DiagnosticProvider.Enable(workspace, DiagnosticProvider.Options.Syntax) @@ -83,8 +84,8 @@ class A Await analyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, CancellationToken.None) Await analyzer.AnalyzeDocumentAsync(document, Nothing, InvocationReasons.Empty, CancellationToken.None) + Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider) Await listenerProvider.GetWaiter(FeatureAttribute.DiagnosticService).CreateWaitTask() - Await listenerProvider.GetWaiter(FeatureAttribute.ErrorSquiggles).CreateWaitTask() Assert.True( diagnosticService.GetDiagnostics(workspace, document.Project.Id, document.Id, Nothing, False, CancellationToken.None).Count() = 1) @@ -104,10 +105,9 @@ class A Using workspace = TestWorkspace.CreateCSharp(code.Value) - Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider) - Dim diagnosticService = New DiagnosticService(listenerProvider) + Dim diagnosticService = DirectCast(workspace.ExportProvider.GetExportedValue(Of IDiagnosticService), DiagnosticService) - Dim miscService = New DefaultDiagnosticAnalyzerService(diagnosticService) + Dim miscService = New DefaultDiagnosticAnalyzerService(New TestDiagnosticAnalyzerService(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()), diagnosticService) Assert.False(miscService.SupportGetDiagnostics) DiagnosticProvider.Enable(workspace, DiagnosticProvider.Options.Semantic) @@ -117,8 +117,8 @@ class A Await analyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, CancellationToken.None) Await analyzer.AnalyzeDocumentAsync(document, Nothing, InvocationReasons.Empty, CancellationToken.None) + Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider) Await listenerProvider.GetWaiter(FeatureAttribute.DiagnosticService).CreateWaitTask() - Await listenerProvider.GetWaiter(FeatureAttribute.ErrorSquiggles).CreateWaitTask() Assert.True( diagnosticService.GetDiagnostics(workspace, document.Project.Id, document.Id, Nothing, False, CancellationToken.None).Count() = 1) @@ -138,10 +138,9 @@ class A Using workspace = TestWorkspace.CreateCSharp(code.Value) - Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider) - Dim diagnosticService = New DiagnosticService(listenerProvider) + Dim diagnosticService = DirectCast(workspace.ExportProvider.GetExportedValue(Of IDiagnosticService), DiagnosticService) - Dim miscService = New DefaultDiagnosticAnalyzerService(diagnosticService) + Dim miscService = New DefaultDiagnosticAnalyzerService(New TestDiagnosticAnalyzerService(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()), diagnosticService) Assert.False(miscService.SupportGetDiagnostics) DiagnosticProvider.Enable(workspace, DiagnosticProvider.Options.Semantic Or DiagnosticProvider.Options.Syntax) @@ -151,8 +150,8 @@ class A Await analyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, CancellationToken.None) Await analyzer.AnalyzeDocumentAsync(document, Nothing, InvocationReasons.Empty, CancellationToken.None) + Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider) Await listenerProvider.GetWaiter(FeatureAttribute.DiagnosticService).CreateWaitTask() - Await listenerProvider.GetWaiter(FeatureAttribute.ErrorSquiggles).CreateWaitTask() Assert.True( diagnosticService.GetDiagnostics(workspace, document.Project.Id, document.Id, Nothing, False, CancellationToken.None).Count() = 2) @@ -172,10 +171,9 @@ class A Using workspace = TestWorkspace.CreateCSharp(code.Value) - Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider) - Dim diagnosticService = New DiagnosticService(listenerProvider) + Dim diagnosticService = DirectCast(workspace.ExportProvider.GetExportedValue(Of IDiagnosticService), DiagnosticService) - Dim miscService = New DefaultDiagnosticAnalyzerService(diagnosticService) + Dim miscService = New DefaultDiagnosticAnalyzerService(New TestDiagnosticAnalyzerService(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()), diagnosticService) Assert.False(miscService.SupportGetDiagnostics) DiagnosticProvider.Enable(workspace, DiagnosticProvider.Options.Semantic Or DiagnosticProvider.Options.Syntax) @@ -187,8 +185,8 @@ class A analyzer.RemoveDocument(document.Id) + Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider) Await listenerProvider.GetWaiter(FeatureAttribute.DiagnosticService).CreateWaitTask() - Await listenerProvider.GetWaiter(FeatureAttribute.ErrorSquiggles).CreateWaitTask() Assert.True( diagnosticService.GetDiagnostics(workspace, document.Project.Id, document.Id, Nothing, False, CancellationToken.None).Count() = 0) @@ -201,7 +199,7 @@ class A class 123 { } Using workspace = TestWorkspace.CreateCSharp(code.Value) - Dim miscService = New DefaultDiagnosticAnalyzerService(New MockDiagnosticUpdateSourceRegistrationService()) + Dim miscService = New DefaultDiagnosticAnalyzerService(New TestDiagnosticAnalyzerService(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()), New MockDiagnosticUpdateSourceRegistrationService()) DiagnosticProvider.Enable(workspace, DiagnosticProvider.Options.Syntax) @@ -226,7 +224,7 @@ Class 123 End Class Using workspace = TestWorkspace.CreateVisualBasic(code.Value) - Dim miscService = New DefaultDiagnosticAnalyzerService(New MockDiagnosticUpdateSourceRegistrationService()) + Dim miscService = New DefaultDiagnosticAnalyzerService(New TestDiagnosticAnalyzerService(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()), New MockDiagnosticUpdateSourceRegistrationService()) DiagnosticProvider.Enable(workspace, DiagnosticProvider.Options.Syntax) @@ -243,5 +241,64 @@ End Class Assert.Equal(PredefinedBuildTools.Live, buildTool) End Using End Function + + + Public Async Function TestMiscDocumentWithNoCompilationWithScriptSemantic() As Task + Dim test = + + + + + Dummy content. + + + + + Dim analyzerMap = ImmutableDictionary(Of String, ImmutableArray(Of DiagnosticAnalyzer)).Empty.Add( + NoCompilationConstants.LanguageName, ImmutableArray.Create(Of DiagnosticAnalyzer)(New DiagnosticAnalyzerWithSemanticError())) + + Using workspace = TestWorkspace.CreateWorkspace(test) + Dim diagnosticService = DirectCast(workspace.ExportProvider.GetExportedValue(Of IDiagnosticService), DiagnosticService) + + Dim miscService = New DefaultDiagnosticAnalyzerService(New TestDiagnosticAnalyzerService(analyzerMap), diagnosticService) + Assert.False(miscService.SupportGetDiagnostics) + + DiagnosticProvider.Enable(workspace, DiagnosticProvider.Options.ScriptSemantic) + + Dim document = workspace.CurrentSolution.Projects.First().Documents.First() + Dim analyzer = miscService.CreateIncrementalAnalyzer(workspace) + + Await analyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, CancellationToken.None) + Await analyzer.AnalyzeDocumentAsync(document, Nothing, InvocationReasons.Empty, CancellationToken.None) + + Dim listenerProvider = workspace.ExportProvider.GetExportedValue(Of IAsynchronousOperationListenerProvider) + Await listenerProvider.GetWaiter(FeatureAttribute.DiagnosticService).CreateWaitTask() + + Assert.True( + diagnosticService.GetDiagnostics(workspace, document.Project.Id, document.Id, Nothing, False, CancellationToken.None).Count() = 1) + End Using + End Function + + + Private Class DiagnosticAnalyzerWithSemanticError + Inherits DocumentDiagnosticAnalyzer + + Public Shared ReadOnly Descriptor As DiagnosticDescriptor = New DiagnosticDescriptor( + "Id", "Error", "Error", "Error", DiagnosticSeverity.Error, isEnabledByDefault:=True) + + Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor) + Get + Return ImmutableArray.Create(Descriptor) + End Get + End Property + + Public Overrides Function AnalyzeSemanticsAsync(document As Document, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of Diagnostic)) + Return Task.FromResult(ImmutableArray.Create(Diagnostic.Create(Descriptor, Location.Create(document.FilePath, Nothing, Nothing)))) + End Function + + Public Overrides Function AnalyzeSyntaxAsync(document As Document, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of Diagnostic)) + Return SpecializedTasks.EmptyImmutableArray(Of Diagnostic)() + End Function + End Class End Class End Namespace diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticProvider.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticProvider.cs index 2cce550c00f9d..8c32efa64b5b3 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticProvider.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticProvider.cs @@ -29,7 +29,8 @@ private static CodeAnalysis.Options.OptionSet GetOptions(Workspace workspace, Op { return workspace.Options .WithChangedOption(InternalRuntimeDiagnosticOptions.Syntax, (options & Options.Syntax) == Options.Syntax) - .WithChangedOption(InternalRuntimeDiagnosticOptions.Semantic, (options & Options.Semantic) == Options.Semantic); + .WithChangedOption(InternalRuntimeDiagnosticOptions.Semantic, (options & Options.Semantic) == Options.Semantic) + .WithChangedOption(InternalRuntimeDiagnosticOptions.ScriptSemantic, (options & Options.ScriptSemantic) == Options.ScriptSemantic); } [Flags] @@ -44,6 +45,11 @@ public enum Options /// Include semantic errors /// Semantic = 0x02, + + /// + /// Include script semantic errors + /// + ScriptSemantic = 0x04, } } } diff --git a/src/Workspaces/Core/Portable/Diagnostics/InternalRuntimeDiagnosticOptions.cs b/src/Workspaces/Core/Portable/Diagnostics/InternalRuntimeDiagnosticOptions.cs index 1cd1077b05069..8345c974740b7 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/InternalRuntimeDiagnosticOptions.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/InternalRuntimeDiagnosticOptions.cs @@ -8,5 +8,6 @@ internal static class InternalRuntimeDiagnosticOptions { public static readonly Option Syntax = new Option(nameof(InternalRuntimeDiagnosticOptions), nameof(Syntax), defaultValue: false); public static readonly Option Semantic = new Option(nameof(InternalRuntimeDiagnosticOptions), nameof(Semantic), defaultValue: false); + public static readonly Option ScriptSemantic = new Option(nameof(InternalRuntimeDiagnosticOptions), nameof(ScriptSemantic), defaultValue: false); } } From 73f242e9ffdbaabac7fa4d1191366b24bceb5e08 Mon Sep 17 00:00:00 2001 From: HeeJae Chang Date: Thu, 29 Nov 2018 13:23:54 -0800 Subject: [PATCH 3/4] enable semantic diagnostics for script file for misc workspace. now, C#/VB script files in misc workspace will get semantic errors as well as syntax errors. any language such as F# if document is added to misc workspace with SourceCodeKind.Script, they will automatically get semantic errors as well. this PR doesn't address changes propagations for #load which was never supported for existing C#/VB script support. --- .../MiscellaneousFilesWorkspace.cs | 26 +++++++++++++++---- .../DocumentState_SimpleProperties.cs | 2 +- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs index edf49ec14955e..cb3d9163e189a 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs @@ -82,7 +82,9 @@ public void RegisterLanguage(Guid languageGuid, string languageName, string scri internal void StartSolutionCrawler() { - DiagnosticProvider.Enable(this, DiagnosticProvider.Options.Syntax); + // misc workspace will enable syntax errors and semantic errors for script files for + // all participating projects in the workspace + DiagnosticProvider.Enable(this, DiagnosticProvider.Options.Syntax | DiagnosticProvider.Options.ScriptSemantic); } internal void StopSolutionCrawler() @@ -352,6 +354,8 @@ private ProjectInfo CreateProjectInfoForDocument(string filePath) var languageInformation = TryGetLanguageInformation(filePath); Contract.ThrowIfNull(languageInformation); + var fileExtension = PathUtilities.GetExtension(filePath); + var languageServices = Services.GetLanguageServices(languageInformation.LanguageName); var compilationOptionsOpt = languageServices.GetService()?.GetDefaultCompilationOptions(); @@ -361,7 +365,7 @@ private ProjectInfo CreateProjectInfoForDocument(string filePath) if (parseOptionsOpt != null && compilationOptionsOpt != null && - PathUtilities.GetExtension(filePath) == languageInformation.ScriptExtension) + fileExtension == languageInformation.ScriptExtension) { parseOptionsOpt = parseOptionsOpt.WithKind(SourceCodeKind.Script); @@ -391,10 +395,11 @@ private ProjectInfo CreateProjectInfoForDocument(string filePath) var projectId = ProjectId.CreateNewId(debugName: "Miscellaneous Files Project for " + filePath); var documentId = DocumentId.CreateNewId(projectId, debugName: filePath); + var sourceCodeKind = GetSourceCodeKind(); var documentInfo = DocumentInfo.Create( documentId, filePath, - sourceCodeKind: parseOptionsOpt?.Kind ?? SourceCodeKind.Regular, + sourceCodeKind: sourceCodeKind, loader: new FileTextLoader(filePath, defaultEncoding: null), filePath: filePath); @@ -414,8 +419,19 @@ private ProjectInfo CreateProjectInfoForDocument(string filePath) metadataReferences: _metadataReferences); // Miscellaneous files projects are never fully loaded since, by definition, it won't know - // what the full set of information is. - return projectInfo.WithHasAllInformation(hasAllInformation: false); + // what the full set of information is except when the file is script code. + return projectInfo.WithHasAllInformation(hasAllInformation: sourceCodeKind == SourceCodeKind.Script); + + SourceCodeKind GetSourceCodeKind() + { + if (parseOptionsOpt != null) + { + return parseOptionsOpt.Kind; + } + + return string.Equals(fileExtension, languageInformation.ScriptExtension, StringComparison.OrdinalIgnoreCase) ? + SourceCodeKind.Script : SourceCodeKind.Regular; + } } private void DetachFromDocument(uint docCookie, string moniker) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_SimpleProperties.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_SimpleProperties.cs index cc10cec4e45ef..e13cf75afe7af 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_SimpleProperties.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState_SimpleProperties.cs @@ -14,7 +14,7 @@ public SourceCodeKind SourceCodeKind { get { - return this.ParseOptions == null ? SourceCodeKind.Regular : this.ParseOptions.Kind; + return this.ParseOptions == null ? this.Attributes.SourceCodeKind : this.ParseOptions.Kind; } } From 415755ffcaecdbdf803b322a8216939fa4a5a987 Mon Sep 17 00:00:00 2001 From: HeeJae Chang Date: Wed, 5 Dec 2018 00:57:26 -0800 Subject: [PATCH 4/4] addressed PR feedbacks --- .../DefaultDiagnosticAnalyzerService.cs | 8 ++++++- .../MiscellaneousFilesWorkspace.cs | 21 +++++++++++-------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs index 6389cfdc8d74a..baf725521cf52 100644 --- a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs @@ -144,7 +144,13 @@ IEnumerable GetAnalyzers() public void RemoveDocument(DocumentId documentId) { - // a file is removed from misc project + // a file is removed from a solution + // + // here syntax and semantic indicates type of errors not where it is originated from. + // Option.Semantic or Option.ScriptSemantic indicates what kind of document we will produce semantic errors from. + // Option.Semantic == true means we will generate semantic errors for all document type + // Option.ScriptSemantic == true means we will generate semantic errors only for script document type + // both of them at the end generates semantic errors RaiseEmptyDiagnosticUpdated(AnalysisKind.Syntax, documentId); RaiseEmptyDiagnosticUpdated(AnalysisKind.Semantic, documentId); } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs index cb3d9163e189a..d9cee0cb56b98 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs @@ -395,7 +395,7 @@ private ProjectInfo CreateProjectInfoForDocument(string filePath) var projectId = ProjectId.CreateNewId(debugName: "Miscellaneous Files Project for " + filePath); var documentId = DocumentId.CreateNewId(projectId, debugName: filePath); - var sourceCodeKind = GetSourceCodeKind(); + var sourceCodeKind = GetSourceCodeKind(parseOptionsOpt, fileExtension, languageInformation); var documentInfo = DocumentInfo.Create( documentId, filePath, @@ -421,17 +421,20 @@ private ProjectInfo CreateProjectInfoForDocument(string filePath) // Miscellaneous files projects are never fully loaded since, by definition, it won't know // what the full set of information is except when the file is script code. return projectInfo.WithHasAllInformation(hasAllInformation: sourceCodeKind == SourceCodeKind.Script); + } - SourceCodeKind GetSourceCodeKind() + private SourceCodeKind GetSourceCodeKind( + ParseOptions parseOptionsOpt, + string fileExtension, + LanguageInformation languageInformation) + { + if (parseOptionsOpt != null) { - if (parseOptionsOpt != null) - { - return parseOptionsOpt.Kind; - } - - return string.Equals(fileExtension, languageInformation.ScriptExtension, StringComparison.OrdinalIgnoreCase) ? - SourceCodeKind.Script : SourceCodeKind.Regular; + return parseOptionsOpt.Kind; } + + return string.Equals(fileExtension, languageInformation.ScriptExtension, StringComparison.OrdinalIgnoreCase) ? + SourceCodeKind.Script : SourceCodeKind.Regular; } private void DetachFromDocument(uint docCookie, string moniker)