File: Diagnostics\AbstractCrossLanguageUserDiagnosticTest.vb
Web Access
Project: src\src\EditorFeatures\Test2\Microsoft.CodeAnalysis.EditorFeatures2.UnitTests.vbproj (Microsoft.CodeAnalysis.EditorFeatures2.UnitTests)
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.
 
Imports System.Collections.Immutable
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeActions
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.Host
Imports Microsoft.CodeAnalysis.LanguageService
Imports Microsoft.CodeAnalysis.UnitTests
Imports Microsoft.CodeAnalysis.UnitTests.Diagnostics
Imports Roslyn.Utilities
Imports Xunit.Abstractions
 
Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics
    <[UseExportProvider]>
    Partial Public MustInherit Class AbstractCrossLanguageUserDiagnosticTest
        Private ReadOnly _outputHelper As ITestOutputHelper
 
        Protected Sub New(Optional outputHelper As ITestOutputHelper = Nothing)
            _outputHelper = outputHelper
        End Sub
 
        Protected Const DestinationDocument = "DestinationDocument"
 
        Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures _
            .AddParts(GetType(WorkspaceTestLogger))
 
        Private Shared ReadOnly s_composition As TestComposition = s_compositionWithMockDiagnosticUpdateSourceRegistrationService _
            .AddParts(GetType(TestAddMetadataReferenceCodeActionOperationFactoryWorkspaceService))
 
        Friend MustOverride Function CreateDiagnosticProviderAndFixer(workspace As Workspace, language As String) As (DiagnosticAnalyzer, CodeFixProvider)
 
        Protected Async Function TestMissing(definition As XElement) As Task
            Using workspace = EditorTestWorkspace.Create(definition, composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService)
                Dim diagnosticAndFix = Await GetDiagnosticAndFixAsync(workspace)
                Assert.Null(diagnosticAndFix)
            End Using
        End Function
 
        Protected Overridable Function MassageActions(actions As IList(Of CodeAction)) As IList(Of CodeAction)
            Return actions
        End Function
 
        Protected Shared Function FlattenActions(codeActions As IEnumerable(Of CodeAction)) As IList(Of CodeAction)
            Return codeActions?.SelectMany(
                Function(a) If(a.NestedActions.Length > 0,
                               a.NestedActions.ToArray(),
                               {a})).ToList()
        End Function
 
        Protected Async Function TestAsync(definition As XElement,
                            Optional expected As String = Nothing,
                            Optional codeActionIndex As Integer = 0,
                            Optional verifyTokens As Boolean = True,
                            Optional fileNameToExpected As Dictionary(Of String, String) = Nothing,
                            Optional verifySolutions As Func(Of Solution, Solution, Task) = Nothing,
                            Optional onAfterWorkspaceCreated As Func(Of EditorTestWorkspace, Task) = Nothing,
                            Optional glyphTags As ImmutableArray(Of String) = Nothing) As Task
            Using workspace = EditorTestWorkspace.CreateWorkspace(definition, composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService)
                If _outputHelper IsNot Nothing Then
                    workspace.Services.SolutionServices.SetWorkspaceTestOutput(_outputHelper)
                End If
 
                If onAfterWorkspaceCreated IsNot Nothing Then
                    Await onAfterWorkspaceCreated(workspace)
                End If
 
                Dim diagnosticAndFix = Await GetDiagnosticAndFixAsync(workspace)
                Dim codeActions As IList(Of CodeAction) = diagnosticAndFix.Item2.Fixes.Select(Function(f) f.Action).ToList()
                codeActions = MassageActions(codeActions)
                Dim codeAction = codeActions(codeActionIndex)
 
                If Not glyphTags.IsDefault Then
                    AssertEx.SetEqual(glyphTags, codeAction.Tags)
                End If
 
                Dim oldSolution = workspace.CurrentSolution
                Dim operations = Await codeAction.GetOperationsAsync(CancellationToken.None)
 
                For Each operation In operations
                    If operation.ApplyDuringTests Then
                        operation.Apply(workspace, CancellationToken.None)
                    End If
                Next
 
                Dim updatedSolution = workspace.CurrentSolution
 
                If verifySolutions IsNot Nothing Then
                    Await verifySolutions(oldSolution, updatedSolution)
                End If
 
                If expected Is Nothing AndAlso
                   fileNameToExpected Is Nothing AndAlso
                   verifySolutions Is Nothing Then
                    Dim projectChanges = SolutionUtilities.GetSingleChangedProjectChanges(oldSolution, updatedSolution)
                    Assert.Empty(projectChanges.GetChangedDocuments())
                ElseIf expected IsNot Nothing Then
                    Dim updatedDocument = SolutionUtilities.GetSingleChangedDocument(oldSolution, updatedSolution)
 
                    Await VerifyAsync(expected, verifyTokens, updatedDocument)
                ElseIf fileNameToExpected IsNot Nothing Then
                    For Each kvp In fileNameToExpected
                        Dim updatedDocument = updatedSolution.Projects.SelectMany(Function(p) p.Documents).Single(Function(d) d.Name = kvp.Key)
                        Await VerifyAsync(kvp.Value, verifyTokens, updatedDocument)
                    Next
                End If
            End Using
        End Function
 
        Private Shared Async Function VerifyAsync(expected As String, verifyTokens As Boolean, updatedDocument As Document) As Task
            Dim actual = (Await updatedDocument.GetTextAsync()).ToString().Trim()
 
            If verifyTokens Then
                Utilities.AssertEx.TokensAreEqual(expected, actual, updatedDocument.Project.Language)
            Else
                AssertEx.Equal(expected, actual)
            End If
        End Function
 
        Friend Async Function GetDiagnosticAndFixAsync(workspace As EditorTestWorkspace) As Task(Of Tuple(Of Diagnostic, CodeFixCollection))
            Return (Await GetDiagnosticAndFixesAsync(workspace)).FirstOrDefault()
        End Function
 
        Private Shared Function GetHostDocument(workspace As EditorTestWorkspace) As EditorTestHostDocument
            Dim hostDocument = workspace.Documents.First(Function(d) d.CursorPosition.HasValue)
 
            Return hostDocument
        End Function
 
        Public Shared Sub AddAnalyzerToWorkspace(workspace As Workspace, analyzer As DiagnosticAnalyzer)
            Dim analyzeReferences As AnalyzerReference()
            If analyzer IsNot Nothing Then
                analyzeReferences = {New AnalyzerImageReference(ImmutableArray.Create(analyzer))}
            Else
                ' create a serializable analyzer reference
                analyzeReferences =
                {
                    New AnalyzerFileReference(DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp).GetType().Assembly.Location, TestAnalyzerAssemblyLoader.LoadFromFile),
                    New AnalyzerFileReference(DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.VisualBasic).GetType().Assembly.Location, TestAnalyzerAssemblyLoader.LoadFromFile)
                }
            End If
 
            workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(analyzeReferences))
        End Sub
 
        Private Async Function GetDiagnosticAndFixesAsync(workspace As EditorTestWorkspace) As Task(Of IEnumerable(Of Tuple(Of Diagnostic, CodeFixCollection)))
            Dim hostDocument = GetHostDocument(workspace)
            Dim providerAndFixer = CreateDiagnosticProviderAndFixer(workspace, hostDocument.Project.Language)
            Dim fixer = providerAndFixer.Item2
 
            AddAnalyzerToWorkspace(workspace, providerAndFixer.Item1)
 
            Dim result = New List(Of Tuple(Of Diagnostic, CodeFixCollection))
            Dim docAndDiagnostics = Await GetDocumentAndDiagnosticsAsync(workspace)
            Dim document = docAndDiagnostics.Item1
 
            Dim ids = New HashSet(Of String)(fixer.FixableDiagnosticIds)
            Dim diagnostics = docAndDiagnostics.Item2.Where(Function(d) ids.Contains(d.Id)).ToList()
            Dim tree = Await document.GetSyntaxTreeAsync()
 
            For Each diagnostic In diagnostics
                Dim fixes = New List(Of CodeFix)
                Dim context = New CodeFixContext(document, diagnostic, Sub(a, d) fixes.Add(New CodeFix(document.Project, a, d)), CancellationToken.None)
                providerAndFixer.Item2.RegisterCodeFixesAsync(context).Wait()
                If fixes.Any() Then
                    result.Add(Tuple.Create(diagnostic, New CodeFixCollection(
                                            fixer, diagnostic.Location.SourceSpan, fixes.ToImmutableArrayOrEmpty(),
                                            fixAllState:=Nothing, supportedScopes:=Nothing, firstDiagnostic:=Nothing)))
                End If
            Next
 
            Return result
        End Function
 
        Private Shared Async Function GetDocumentAndDiagnosticsAsync(workspace As EditorTestWorkspace) As Task(Of Tuple(Of Document, IEnumerable(Of Diagnostic)))
            Dim hostDocument = GetHostDocument(workspace)
 
            Dim invocationBuffer = hostDocument.GetTextBuffer()
            Dim invocationPoint = workspace.Documents.Single(Function(d) d.CursorPosition.HasValue AndAlso Not d.IsLinkFile).CursorPosition.Value
 
            Dim document = workspace.CurrentSolution.GetDocument(hostDocument.Id)
 
            Dim syntaxFacts = document.GetLanguageService(Of ISyntaxFactsService)()
            Dim root = Await document.GetSyntaxRootAsync()
            Dim start = syntaxFacts.GetContainingMemberDeclaration(root, invocationPoint)
 
            Dim result = Await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(workspace, document, start.FullSpan)
 
            '' currently, we don't test compilation level user diagnostic
            Return Tuple.Create(document, result.Where(Function(d) d.Location.SourceSpan.IntersectsWith(invocationPoint)))
        End Function
 
        Protected Async Function TestAddProjectReferenceAsync(xmlDefinition As XElement,
                                              expectedProjectReferenceFrom As String,
                                              expectedProjectReferenceTo As String,
                                              Optional index As Integer = 0) As Task
 
            Using workspace = EditorTestWorkspace.Create(xmlDefinition, composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService)
                Dim diagnosticAndFix = Await GetDiagnosticAndFixAsync(workspace)
                Dim codeAction = diagnosticAndFix.Item2.Fixes.ElementAt(index).Action
                Dim operations = Await codeAction.GetOperationsAsync(CancellationToken.None)
                Dim edit = operations.OfType(Of ApplyChangesOperation)().First()
                Dim addedProjectReference = SolutionUtilities.GetSingleAddedProjectReference(workspace.CurrentSolution, edit.ChangedSolution)
 
                Assert.Equal(expectedProjectReferenceFrom, addedProjectReference.Item1.Name)
 
                Dim projectTo = edit.ChangedSolution.GetProject(addedProjectReference.Item2.ProjectId)
                Assert.Equal(expectedProjectReferenceTo, projectTo.Name)
            End Using
        End Function
 
        Protected Async Function TestAddUnresolvedMetadataReferenceAsync(xmlDefinition As XElement,
                                                         expectedProjectToReceiveReference As String,
                                                         expectedAssemblyIdentity As String,
                                                         Optional index As Integer = 0) As Task
 
            Using workspace = EditorTestWorkspace.Create(xmlDefinition, composition:=s_composition)
                Dim diagnosticAndFix = Await GetDiagnosticAndFixAsync(workspace)
                Dim codeAction = diagnosticAndFix.Item2.Fixes.ElementAt(index).Action
                Dim operations = Await codeAction.GetOperationsAsync(CancellationToken.None)
 
                Dim edit = operations.OfType(Of ApplyChangesOperation)().FirstOrDefault()
                Assert.Equal(Nothing, edit)
 
                Dim postOp = operations.OfType(Of TestAddMetadataReferenceCodeActionOperationFactoryWorkspaceService.Operation).FirstOrDefault()
                Assert.NotEqual(Nothing, postOp)
                Assert.Equal(expectedAssemblyIdentity, postOp.AssemblyIdentity.GetDisplayName())
                Assert.Equal(expectedProjectToReceiveReference, workspace.CurrentSolution.GetProject(postOp.ProjectId).Name)
            End Using
        End Function
 
        Protected Overridable Function GetNode(doc As Document, position As Integer) As SyntaxNode
            Return doc.GetSyntaxRootAsync().Result.FindToken(position).Parent
        End Function
    End Class
End Namespace