diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 314f1aaab69185b046e9b657bf331d7d7e73d260..c921ea6f5a307c71bd5bc682f5f468bd34c58645 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -5,9 +5,11 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Composition; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -138,6 +140,71 @@ public async Task TestOpenFileOnlyAnalyzerDiagnostics() await listener.CreateWaitTask().ConfigureAwait(false); } + [Fact] + public async Task TestSynchronizeWithBuild() + { + var workspace = new AdhocWorkspace(MefV1HostServices.Create(TestExportProvider.ExportProviderWithCSharpAndVisualBasic.AsExportProvider())); + + var language = Workspaces.NoCompilationConstants.LanguageName; + + var project = workspace.AddProject( + ProjectInfo.Create( + ProjectId.CreateNewId(), + VersionStamp.Create(), + "NoNameProject", + "NoNameProject", + language)); + + var filePath = "NoNameDoc.other"; + var document = workspace.AddDocument( + DocumentInfo.Create( + DocumentId.CreateNewId(project.Id), + "Empty", + loader: TextLoader.From(TextAndVersion.Create(SourceText.From(""), VersionStamp.Create(), filePath)), + filePath: filePath)); + + // create listener/service/analyzer + var listener = new AsynchronousOperationListener(); + var service = new MyDiagnosticAnalyzerService(new NoNameAnalyzer(), listener, language); + var analyzer = service.CreateIncrementalAnalyzer(workspace); + + bool syntax = false; + + // listen to events + service.DiagnosticsUpdated += (s, a) => + { + switch (a.Diagnostics.Length) + { + case 0: + return; + case 1: + syntax |= a.Diagnostics[0].Id == NoNameAnalyzer.s_syntaxRule.Id; + return; + default: + AssertEx.Fail("shouldn't reach here"); + return; + } + }; + + // cause analysis + await service.SynchronizeWithBuildAsync( + workspace, + ImmutableDictionary>.Empty.Add( + document.Project.Id, + ImmutableArray.Create( + Diagnostic.Create( + NoNameAnalyzer.s_syntaxRule, + Location.Create(document.FilePath, TextSpan.FromBounds(0, 0), new LinePositionSpan(new LinePosition(0, 0), new LinePosition(0, 0)))).ToDiagnosticData(project)))); + + // wait for all events to raised + await listener.CreateWaitTask().ConfigureAwait(false); + + // two should have been called. + Assert.True(syntax); + + // we should reach here without crashing + } + private static Document GetDocumentFromIncompleteProject(AdhocWorkspace workspace) { var project = workspace.AddProject( @@ -160,11 +227,11 @@ private static async Task RunAllAnalysisAsync(IIncrementalAnalyzer analyzer, Doc private class MyDiagnosticAnalyzerService : DiagnosticAnalyzerService { - internal MyDiagnosticAnalyzerService(DiagnosticAnalyzer analyzer, IAsynchronousOperationListener listener) + internal MyDiagnosticAnalyzerService(DiagnosticAnalyzer analyzer, IAsynchronousOperationListener listener, string language = LanguageNames.CSharp) : base(new HostAnalyzerManager( ImmutableArray.Create( new TestAnalyzerReferenceByLanguage( - ImmutableDictionary>.Empty.Add(LanguageNames.CSharp, ImmutableArray.Create(analyzer)))), + ImmutableDictionary>.Empty.Add(language, ImmutableArray.Create(analyzer)))), hostDiagnosticUpdateSource: null), hostDiagnosticUpdateSource: null, registrationService: new MockDiagnosticUpdateSourceRegistrationService(), @@ -210,5 +277,23 @@ public bool OpenFileOnly(Workspace workspace) return true; } } + + private class NoNameAnalyzer : DocumentDiagnosticAnalyzer + { + internal static readonly DiagnosticDescriptor s_syntaxRule = new DiagnosticDescriptor("syntax", "test", "test", "test", DiagnosticSeverity.Error, isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(s_syntaxRule); + + public override async Task> AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + return ImmutableArray.Create(Diagnostic.Create(s_syntaxRule, Location.Create(document.FilePath, TextSpan.FromBounds(0, 0), new LinePositionSpan(new LinePosition(0, 0), new LinePosition(0, 0))))); + } + + public override Task> AnalyzeSemanticsAsync(Document document, CancellationToken cancellationToken) + { + return SpecializedTasks.Default>(); + } + } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs index 47f71506ce0f58870c00b3207a4732f81d04d597..117a97f1c1618ccd63e55539590e4cb58b8be204 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -122,6 +122,14 @@ public StateSet GetOrCreateStateSet(Project project, DiagnosticAnalyzer analyzer /// public ImmutableArray CreateBuildOnlyProjectStateSet(Project project) { + if (!project.SupportsCompilation) + { + // languages which don't use our compilation model but diagnostic framework, + // all their analyzer should be host analyzers. return all host analyzers + // for the language + return _hostStates.GetOrCreateStateSets(project.Language).ToImmutableArray(); + } + // create project analyzer reference identity map var referenceIdentities = project.AnalyzerReferences.Select(r => _analyzerManager.GetAnalyzerReferenceIdentity(r)).ToSet();