Skip to content

Commit

Permalink
IDE support for analyzing and reporting diagnostics in non-source files
Browse files Browse the repository at this point in the history
Fix tests
  • Loading branch information
mavasani committed Jun 22, 2020
1 parent 450cf6a commit d2d197f
Show file tree
Hide file tree
Showing 31 changed files with 509 additions and 176 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public void DiagnosticAnalyzerAllInOne()
missingSyntaxKinds.Add(SyntaxKind.FunctionPointerType);

var analyzer = new CSharpTrackingDiagnosticAnalyzer();
CreateCompilationWithMscorlib45(source).VerifyAnalyzerDiagnostics(new[] { analyzer });
var options = new AnalyzerOptions(new[] { new TestAdditionalText() }.ToImmutableArray<AdditionalText>());
CreateCompilationWithMscorlib45(source).VerifyAnalyzerDiagnostics(new[] { analyzer }, options);
analyzer.VerifyAllAnalyzerMembersWereCalled();
analyzer.VerifyAnalyzeSymbolCalledForAllSymbolKinds();
analyzer.VerifyAnalyzeNodeCalledForAllSyntaxKinds(missingSyntaxKinds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Semantics
Public Sub DiagnosticAnalyzerAllInOne()
Dim source = TestResource.AllInOneVisualBasicBaseline
Dim analyzer = New BasicTrackingDiagnosticAnalyzer()
CreateCompilationWithMscorlib40({source}).VerifyAnalyzerDiagnostics({analyzer})
Dim options = New AnalyzerOptions({DirectCast(New TestAdditionalText(), AdditionalText)}.ToImmutableArray())
CreateCompilationWithMscorlib40({source}).VerifyAnalyzerDiagnostics({analyzer}, options)
analyzer.VerifyAllAnalyzerMembersWereCalled()
analyzer.VerifyAnalyzeSymbolCalledForAllSymbolKinds()
analyzer.VerifyAnalyzeNodeCalledForAllSyntaxKinds(New HashSet(Of SyntaxKind)())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ public async Task DiagnosticAnalyzerDriverAllInOne()
using var workspace = TestWorkspace.CreateCSharp(source, TestOptions.Regular);

var analyzerReference = new AnalyzerImageReference(ImmutableArray.Create<DiagnosticAnalyzer>(analyzer));
workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference }));
var newSolution = workspace.CurrentSolution.WithAnalyzerReferences(new[] { analyzerReference })
.Projects.Single().AddAdditionalDocument(name: "dummy.txt", text: "", filePath: "dummy.txt").Project.Solution;
workspace.TryApplyChanges(newSolution);

var document = workspace.CurrentSolution.Projects.Single().Documents.Single();
AccessSupportedDiagnostics(analyzer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Roslyn.Utilities;
Expand Down Expand Up @@ -73,7 +74,7 @@ public void Register(Workspace workspace)
private async Task AnalyzeAsync()
{
var workerBackOffTimeSpanInMS = _workspace.Options.GetOption(InternalSolutionCrawlerOptions.PreviewBackOffTimeSpanInMS);
var diagnosticAnalyzer = _owner._analyzerService.CreateIncrementalAnalyzer(_workspace);
var incrementalAnalyzer = _owner._analyzerService.CreateIncrementalAnalyzer(_workspace);

var solution = _workspace.CurrentSolution;
var documentIds = _workspace.GetOpenDocumentIds().ToImmutableArray();
Expand All @@ -82,8 +83,8 @@ private async Task AnalyzeAsync()
{
foreach (var documentId in documentIds)
{
var document = solution.GetDocument(documentId);
if (document == null)
var textDocument = solution.GetTextDocument(documentId);
if (textDocument == null)
{
continue;
}
Expand All @@ -92,8 +93,15 @@ private async Task AnalyzeAsync()
await Task.Delay(workerBackOffTimeSpanInMS, _source.Token).ConfigureAwait(false);

// do actual analysis
await diagnosticAnalyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, _source.Token).ConfigureAwait(false);
await diagnosticAnalyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: _source.Token).ConfigureAwait(false);
if (textDocument is Document document)
{
await incrementalAnalyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, _source.Token).ConfigureAwait(false);
await incrementalAnalyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: _source.Token).ConfigureAwait(false);
}
else if (incrementalAnalyzer is IIncrementalAnalyzer2 incrementalAnalyzer2)
{
await incrementalAnalyzer2.AnalyzeNonSourceDocumentAsync(textDocument, InvocationReasons.Empty, _source.Token).ConfigureAwait(false);
}

// don't call project one.
}
Expand Down
116 changes: 112 additions & 4 deletions src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
Expand Down Expand Up @@ -564,6 +565,105 @@ private async Task TestFullSolutionAnalysisForProjectAsync(Project project, bool
Assert.Equal(expectAnalyzerExecuted, called);
}

[Theory, CombinatorialData]
internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool testMultiple, BackgroundAnalysisScope analysisScope)
{
using var workspace = new AdhocWorkspace();
var options = workspace.Options.WithChangedOption(SolutionCrawlerOptions.BackgroundAnalysisScopeOption, LanguageNames.CSharp, analysisScope);
workspace.SetOptions(options);

var projectInfo = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), "CSharpProject", "CSharpProject", LanguageNames.CSharp);
var project = workspace.AddProject(projectInfo);

var diagnosticSpan = new TextSpan(2, 2);
var analyzer = new AdditionalFileAnalyzer(registerFromInitialize, diagnosticSpan, id: "ID0001");
var analyzers = ImmutableArray.Create<DiagnosticAnalyzer>(analyzer);
if (testMultiple)
{
analyzer = new AdditionalFileAnalyzer2(registerFromInitialize, diagnosticSpan, id: "ID0002");
analyzers = analyzers.Add(analyzer);
}

var analyzerReference = new AnalyzerImageReference(analyzers);
project = project.WithAnalyzerReferences(new[] { analyzerReference })
.AddAdditionalDocument(name: "dummy.txt", text: "Additional File Text", filePath: "dummy.txt").Project;
if (testMultiple)
{
project = project.AddAdditionalDocument(name: "dummy2.txt", text: "Additional File2 Text", filePath: "dummy2.txt").Project;
}

var applied = workspace.TryApplyChanges(project.Solution);
Assert.True(applied);

// create listener/service/analyzer
var listener = new AsynchronousOperationListener();
var service = new MyDiagnosticAnalyzerService(listener);

var diagnostics = new ConcurrentSet<DiagnosticData>();
service.DiagnosticsUpdated += (s, e) =>
{
diagnostics.AddRange(e.Diagnostics);
};

var incrementalAnalyzer = (DiagnosticIncrementalAnalyzer)service.CreateIncrementalAnalyzer(workspace);
var firstAdditionalDocument = project.AdditionalDocuments.FirstOrDefault();

switch (analysisScope)
{
case BackgroundAnalysisScope.ActiveFile:
case BackgroundAnalysisScope.OpenFilesAndProjects:
workspace.OpenAdditionalDocument(firstAdditionalDocument.Id);
await incrementalAnalyzer.AnalyzeNonSourceDocumentAsync(firstAdditionalDocument, InvocationReasons.SyntaxChanged, CancellationToken.None);
break;

case BackgroundAnalysisScope.FullSolution:
await incrementalAnalyzer.AnalyzeProjectAsync(project, semanticsChanged: true, InvocationReasons.Reanalyze, CancellationToken.None);
break;

default:
throw ExceptionUtilities.UnexpectedValue(analysisScope);
}

await listener.ExpeditedWaitAsync();

var expectedCount = !testMultiple
? 1
: analysisScope == BackgroundAnalysisScope.FullSolution ? 4 : 2;
Assert.Equal(expectedCount, diagnostics.Count);

for (var i = 0; i < analyzers.Length; i++)
{
analyzer = (AdditionalFileAnalyzer)analyzers[i];
foreach (var additionalDoc in project.AdditionalDocuments)
{
var applicableDiagnostics = diagnostics.Where(
d => d.Id == analyzer.Descriptor.Id && d.DataLocation.OriginalFilePath == additionalDoc.FilePath);

if (analysisScope != BackgroundAnalysisScope.FullSolution &&
firstAdditionalDocument != additionalDoc)
{
Assert.Empty(applicableDiagnostics);
}
else
{
var diagnostic = Assert.Single(applicableDiagnostics);
Assert.Equal(diagnosticSpan, diagnostic.GetTextSpan());
diagnostics.Remove(diagnostic);
}
}
}

Assert.Empty(diagnostics);
}

private class AdditionalFileAnalyzer2 : AdditionalFileAnalyzer
{
public AdditionalFileAnalyzer2(bool registerFromInitialize, TextSpan diagnosticSpan, string id)
: base(registerFromInitialize, diagnosticSpan, id)
{
}
}

[Theory, CombinatorialData]
internal async Task TestDiagnosticSuppressor(bool includeAnalyzer, bool includeSuppressor, BackgroundAnalysisScope analysisScope)
{
Expand Down Expand Up @@ -678,11 +778,19 @@ private static (bool, bool) CompilerAnalyzerResultSetter(bool syntax, bool seman
return (syntax, semantic);
}

private static async Task RunAllAnalysisAsync(IIncrementalAnalyzer analyzer, Document document)
private static async Task RunAllAnalysisAsync(IIncrementalAnalyzer analyzer, TextDocument textDocument)
{
await analyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, CancellationToken.None).ConfigureAwait(false);
await analyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false);
await analyzer.AnalyzeProjectAsync(document.Project, semanticsChanged: true, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false);
if (textDocument is Document document)
{
await analyzer.AnalyzeSyntaxAsync(document, InvocationReasons.Empty, CancellationToken.None).ConfigureAwait(false);
await analyzer.AnalyzeDocumentAsync(document, bodyOpt: null, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false);
}
else if (analyzer is IIncrementalAnalyzer2 analyzer2)
{
await analyzer2.AnalyzeNonSourceDocumentAsync(textDocument, InvocationReasons.Empty, CancellationToken.None).ConfigureAwait(false);
}

await analyzer.AnalyzeProjectAsync(textDocument.Project, semanticsChanged: true, reasons: InvocationReasons.Empty, cancellationToken: CancellationToken.None).ConfigureAwait(false);
}

private class MyDiagnosticAnalyzerService : DiagnosticAnalyzerService
Expand Down
7 changes: 3 additions & 4 deletions src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,7 @@ protected override void ApplyDocumentAdded(DocumentInfo info, SourceText text)
var hostProject = this.GetTestProject(info.Id.ProjectId);
var hostDocument = new TestHostDocument(
text.ToString(), info.Name, info.SourceCodeKind,
info.Id, folders: info.Folders,
exportProvider: ExportProvider);
info.Id, folders: info.Folders, exportProvider: ExportProvider);
hostProject.AddDocument(hostDocument);
this.OnDocumentAdded(hostDocument.ToDocumentInfo());
}
Expand All @@ -330,7 +329,7 @@ protected override void ApplyAdditionalDocumentTextChanged(DocumentId document,
protected override void ApplyAdditionalDocumentAdded(DocumentInfo info, SourceText text)
{
var hostProject = this.GetTestProject(info.Id.ProjectId);
var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id);
var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id, exportProvider: ExportProvider);
hostProject.AddAdditionalDocument(hostDocument);
this.OnAdditionalDocumentAdded(hostDocument.ToDocumentInfo());
}
Expand All @@ -352,7 +351,7 @@ protected override void ApplyAnalyzerConfigDocumentTextChanged(DocumentId docume
protected override void ApplyAnalyzerConfigDocumentAdded(DocumentInfo info, SourceText text)
{
var hostProject = this.GetTestProject(info.Id.ProjectId);
var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id, filePath: info.FilePath, folders: info.Folders);
var hostDocument = new TestHostDocument(text.ToString(), info.Name, id: info.Id, filePath: info.FilePath, folders: info.Folders, exportProvider: ExportProvider);
hostProject.AddAnalyzerConfigDocument(hostDocument);
this.OnAnalyzerConfigDocumentAdded(hostDocument.ToDocumentInfo());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ Public Class DiagnosticAnalyzerDriverTests
Dim analyzer = New BasicTrackingDiagnosticAnalyzer()
Using workspace = TestWorkspace.CreateVisualBasic(source)
Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer))
workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference}))
Dim newSolution = workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference}).
Projects.Single().AddAdditionalDocument(name:="dummy.txt", text:="", filePath:="dummy.txt").Project.Solution
workspace.TryApplyChanges(newSolution)

Dim document = workspace.CurrentSolution.Projects.Single().Documents.Single()
AccessSupportedDiagnostics(analyzer)
Expand Down
Loading

0 comments on commit d2d197f

Please sign in to comment.