Permalink
Browse files

support semantic errors for script files in misc projects. (#31134)

* 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

* 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.

* 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.

* addressed PR feedbacks
  • Loading branch information...
heejaechang committed Dec 6, 2018
1 parent 7a31585 commit c64963b60e28c8f3577c5424571656656308f066

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -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<DiagnosticsUpdatedArgs> 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<DiagnosticData> 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,79 @@ 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<DiagnosticData>.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<DiagnosticData>.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<DiagnosticAnalyzer> 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);
// 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);
}
public Task DocumentResetAsync(Document document, CancellationToken cancellationToken)
@@ -139,7 +167,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 +196,7 @@ private class DefaultUpdateArgsId : BuildToolId.Base<int, DocumentId>, 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;
}
@@ -262,12 +262,12 @@ public bool ContainsDiagnostics(Workspace workspace, ProjectId projectId)
}
// virtual for testing purposes.
internal virtual Action<Exception, DiagnosticAnalyzer, Diagnostic> GetOnAnalyzerException(ProjectId projectId, DiagnosticLogAggregator diagnosticLogAggregator)
internal virtual Action<Exception, DiagnosticAnalyzer, Diagnostic> 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);
};
@@ -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
{
/// <summary>
/// enum for each analysis kind.
/// </summary>
internal enum AnalysisKind
{
Syntax,
Semantic,
NonLocal
}
}

This file was deleted.

Oops, something went wrong.
@@ -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<CompilationWithAnalyzers> CreateAnalyzerDriverAsync(Project project,
return CreateAnalyzerDriverAsync(project, analyzers, includeSuppressedDiagnostics, cancellationToken);
}
public async Task<CompilationWithAnalyzers> CreateAnalyzerDriverAsync(
public Task<CompilationWithAnalyzers> CreateAnalyzerDriverAsync(
Project project, IEnumerable<DiagnosticAnalyzer> 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<DiagnosticAnalyzer> 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<Exception, bool> 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<Exception, DiagnosticAnalyzer, Diagnostic> 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()
{
Oops, something went wrong.

0 comments on commit c64963b

Please sign in to comment.