File: AddImport\VisualBasicAddImportFeatureService.vb
Web Access
Project: src\src\Features\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.Features.vbproj (Microsoft.CodeAnalysis.VisualBasic.Features)
' 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.Composition
Imports System.Threading
Imports Microsoft.CodeAnalysis.AddImport
Imports Microsoft.CodeAnalysis.CaseCorrection
Imports Microsoft.CodeAnalysis.CodeGeneration
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Editing
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.LanguageService
Imports Microsoft.CodeAnalysis.Simplification
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.AddImport
    <ExportLanguageService(GetType(IAddImportFeatureService), LanguageNames.VisualBasic), [Shared]>
    Friend Class VisualBasicAddImportFeatureService
        Inherits AbstractAddImportFeatureService(Of SimpleNameSyntax)
 
        <ImportingConstructor>
        <Obsolete(MefConstruction.ImportingConstructorMessage, True)>
        Public Sub New()
        End Sub
 
        Protected Overrides Function CanAddImport(node As SyntaxNode, allowInHiddenRegions As Boolean, cancellationToken As CancellationToken) As Boolean
            cancellationToken.ThrowIfCancellationRequested()
            Return node.CanAddImportsStatements(allowInHiddenRegions, cancellationToken)
        End Function
 
        Protected Overrides Function CanAddImportForMethod(
                diagnosticId As String,
                syntaxFacts As ISyntaxFacts,
                node As SyntaxNode,
                ByRef nameNode As SimpleNameSyntax) As Boolean
            Select Case diagnosticId
                Case AddImportDiagnosticIds.BC30456,
                     AddImportDiagnosticIds.BC30390,
                     AddImportDiagnosticIds.BC42309,
                     AddImportDiagnosticIds.BC30451
                    Exit Select
                Case AddImportDiagnosticIds.BC30512
                    ' look up its corresponding method name
                    Dim parent = node.GetAncestor(Of InvocationExpressionSyntax)()
                    If parent Is Nothing Then
                        Return False
                    End If
 
                    Dim method = TryCast(parent.Expression, MemberAccessExpressionSyntax)
                    If method IsNot Nothing Then
                        node = method.Name
                    Else
                        node = parent.Expression
                    End If
 
                    Exit Select
                Case AddImportDiagnosticIds.BC36719
                    If node.IsKind(SyntaxKind.ObjectCollectionInitializer) Then
                        Return True
                    End If
 
                    Return False
                Case AddImportDiagnosticIds.BC32016
                    Dim memberAccessName = TryCast(node, MemberAccessExpressionSyntax)?.Name
                    Dim conditionalAccessName = TryCast(TryCast(TryCast(node, ConditionalAccessExpressionSyntax)?.WhenNotNull, InvocationExpressionSyntax)?.Expression, MemberAccessExpressionSyntax)?.Name
 
                    If memberAccessName Is Nothing AndAlso conditionalAccessName Is Nothing Then
                        Return False
                    End If
 
                    node = If(memberAccessName Is Nothing, conditionalAccessName, memberAccessName)
                    Exit Select
                Case Else
                    Return False
            End Select
 
            Dim memberAccess = TryCast(node, MemberAccessExpressionSyntax)
            If memberAccess IsNot Nothing Then
                node = memberAccess.Name
            End If
 
            If memberAccess.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) Then
                Return False
            End If
 
            nameNode = TryCast(node, SimpleNameSyntax)
            If nameNode Is Nothing Then
                Return False
            End If
 
            Return True
        End Function
 
        Protected Overrides Function CanAddImportForNamespace(diagnosticId As String, node As SyntaxNode, ByRef nameNode As SimpleNameSyntax) As Boolean
            Select Case diagnosticId
                Case AddImportDiagnosticIds.BC30002,
                     IDEDiagnosticIds.UnboundIdentifierId,
                     AddImportDiagnosticIds.BC30451
                    Exit Select
                Case Else
                    Return False
            End Select
 
            Return CanAddImportForTypeOrNamespaceCore(node, nameNode)
        End Function
 
        Protected Overrides Function CanAddImportForDeconstruct(diagnosticId As String, node As SyntaxNode) As Boolean
            ' Not supported yet.
            Return False
        End Function
 
        Protected Overrides Function CanAddImportForGetAwaiter(diagnosticId As String, syntaxFactsService As ISyntaxFacts, node As SyntaxNode) As Boolean
            Return diagnosticId = AddImportDiagnosticIds.BC36930 AndAlso
                AncestorOrSelfIsAwaitExpression(syntaxFactsService, node)
        End Function
 
        Protected Overrides Function CanAddImportForGetEnumerator(diagnosticId As String, syntaxFactsService As ISyntaxFacts, node As SyntaxNode) As Boolean
            Return False
        End Function
 
        Protected Overrides Function CanAddImportForGetAsyncEnumerator(diagnosticId As String, syntaxFactsService As ISyntaxFacts, node As SyntaxNode) As Boolean
            Return False
        End Function
 
        Protected Overrides Function CanAddImportForQuery(diagnosticId As String, node As SyntaxNode) As Boolean
            Return diagnosticId = AddImportDiagnosticIds.BC36593 AndAlso
                node.GetAncestor(Of QueryExpressionSyntax)() IsNot Nothing
        End Function
 
        Protected Overrides Function CanAddImportForType(
                diagnosticId As String, node As SyntaxNode, ByRef nameNode As SimpleNameSyntax) As Boolean
            Select Case diagnosticId
                Case AddImportDiagnosticIds.BC30002,
                     IDEDiagnosticIds.UnboundIdentifierId,
                     AddImportDiagnosticIds.BC30451,
                     AddImportDiagnosticIds.BC32042,
                     AddImportDiagnosticIds.BC32045,
                     AddImportDiagnosticIds.BC30389,
                     AddImportDiagnosticIds.BC31504,
                     AddImportDiagnosticIds.BC36610,
                     AddImportDiagnosticIds.BC30182
                    Exit Select
                Case AddImportDiagnosticIds.BC42309
                    Select Case node.Kind
                        Case SyntaxKind.XmlCrefAttribute
                            node = CType(node, XmlCrefAttributeSyntax).Reference.DescendantNodes().OfType(Of IdentifierNameSyntax).FirstOrDefault()
                        Case SyntaxKind.CrefReference
                            node = CType(node, CrefReferenceSyntax).DescendantNodes().OfType(Of IdentifierNameSyntax).FirstOrDefault()
                    End Select
                Case Else
                    Return False
            End Select
 
            Return CanAddImportForTypeOrNamespaceCore(node, nameNode)
        End Function
 
        Private Shared Function CanAddImportForTypeOrNamespaceCore(node As SyntaxNode, ByRef nameNode As SimpleNameSyntax) As Boolean
            Dim qn = TryCast(node, QualifiedNameSyntax)
            If qn IsNot Nothing Then
                node = GetLeftMostSimpleName(qn)
            End If
 
            nameNode = TryCast(node, SimpleNameSyntax)
            Return nameNode.LooksLikeStandaloneTypeName()
        End Function
 
        Private Shared Function GetLeftMostSimpleName(qn As QualifiedNameSyntax) As SimpleNameSyntax
            While (qn IsNot Nothing)
                Dim left = qn.Left
                Dim simpleName = TryCast(left, SimpleNameSyntax)
                If simpleName IsNot Nothing Then
                    Return simpleName
                End If
 
                qn = TryCast(left, QualifiedNameSyntax)
            End While
 
            Return Nothing
        End Function
 
        Protected Overrides Function GetDescription(nameParts As IReadOnlyList(Of String)) As String
            Return $"Imports { String.Join(".", nameParts) }"
        End Function
 
        Protected Overrides Function GetDescription(
            document As Document,
            options As AddImportPlacementOptions,
            symbol As INamespaceOrTypeSymbol,
            semanticModel As SemanticModel,
            root As SyntaxNode,
            cancellationToken As CancellationToken) As (description As String, hasExistingImport As Boolean)
 
            Dim importsStatement = GetImportsStatement(symbol)
            Dim addImportService = document.GetLanguageService(Of IAddImportsService)
            Dim generator = SyntaxGenerator.GetGenerator(document)
            Return ($"Imports {symbol.ToDisplayString()}",
                    addImportService.HasExistingImport(semanticModel.Compilation, root, root, importsStatement, generator))
        End Function
 
        Private Shared Function GetImportsStatement(symbol As INamespaceOrTypeSymbol) As ImportsStatementSyntax
            Dim nameSyntax = DirectCast(symbol.GenerateTypeSyntax(addGlobal:=False), NameSyntax)
            Return GetImportsStatement(nameSyntax)
        End Function
 
        Private Shared Function GetImportsStatement(nameSyntax As NameSyntax) As ImportsStatementSyntax
            nameSyntax = nameSyntax.WithAdditionalAnnotations(Simplifier.Annotation)
 
            Dim memberImportsClause = SyntaxFactory.SimpleImportsClause(nameSyntax)
            Dim newImport = SyntaxFactory.ImportsStatement(
                importsClauses:=SyntaxFactory.SingletonSeparatedList(Of ImportsClauseSyntax)(memberImportsClause))
 
            Return newImport
        End Function
 
        Protected Overrides Function GetImportNamespacesInScope(semanticModel As SemanticModel, node As SyntaxNode, cancellationToken As CancellationToken) As ISet(Of INamespaceSymbol)
            Return semanticModel.GetImportNamespacesInScope(node)
        End Function
 
        Protected Overrides Function GetDeconstructInfo(semanticModel As SemanticModel, node As SyntaxNode, cancellationToken As CancellationToken) As ITypeSymbol
            Return Nothing
        End Function
 
        Protected Overrides Function GetQueryClauseInfo(
                semanticModel As SemanticModel,
                node As SyntaxNode,
                cancellationToken As CancellationToken) As ITypeSymbol
 
            Dim query = TryCast(node, QueryExpressionSyntax)
 
            If query Is Nothing Then
                query = node.GetAncestor(Of QueryExpressionSyntax)()
            End If
 
            For Each clause In query.Clauses
                If TypeOf clause Is AggregateClauseSyntax Then
                    Dim aggregateClause = DirectCast(clause, AggregateClauseSyntax)
                    Dim aggregateInfo = semanticModel.GetAggregateClauseSymbolInfo(aggregateClause, cancellationToken)
                    If IsValid(aggregateInfo.Select1) OrElse IsValid(aggregateInfo.Select2) Then
                        Return Nothing
                    End If
 
                    For Each variable In aggregateClause.AggregationVariables
                        Dim info = semanticModel.GetSymbolInfo(variable.Aggregation, cancellationToken)
                        If IsValid(info) Then
                            Return Nothing
                        End If
                    Next
                Else
                    Dim symbolInfo = semanticModel.GetSymbolInfo(clause, cancellationToken)
                    If IsValid(symbolInfo) Then
                        Return Nothing
                    End If
                End If
            Next
 
            Dim type As ITypeSymbol
            Dim fromOrAggregateClause = query.Clauses.First()
            If TypeOf fromOrAggregateClause Is FromClauseSyntax Then
                Dim fromClause = DirectCast(fromOrAggregateClause, FromClauseSyntax)
                type = semanticModel.GetTypeInfo(fromClause.Variables.First().Expression, cancellationToken).Type
            Else
                Dim aggregateClause = DirectCast(fromOrAggregateClause, AggregateClauseSyntax)
                type = semanticModel.GetTypeInfo(aggregateClause.Variables.First().Expression, cancellationToken).Type
            End If
 
            Return type
        End Function
 
        Private Shared Function IsValid(info As SymbolInfo) As Boolean
            Dim symbol = info.Symbol.GetOriginalUnreducedDefinition()
            Return symbol IsNot Nothing AndAlso symbol.Locations.Length > 0
        End Function
 
        Protected Overloads Overrides Async Function AddImportAsync(
                contextNode As SyntaxNode,
                symbol As INamespaceOrTypeSymbol,
                document As Document,
                options As AddImportPlacementOptions,
                cancellationToken As CancellationToken) As Task(Of Document)
 
            Dim importsStatement = GetImportsStatement(symbol)
 
            Return Await AddImportAsync(contextNode, document, importsStatement, options, cancellationToken).ConfigureAwait(False)
        End Function
 
        Private Overloads Shared Async Function AddImportAsync(
                contextNode As SyntaxNode,
                document As Document,
                importsStatement As ImportsStatementSyntax,
                options As AddImportPlacementOptions,
                cancellationToken As CancellationToken) As Task(Of Document)
 
            Dim compilation = Await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(False)
            Dim importService = document.GetLanguageService(Of IAddImportsService)
            Dim generator = SyntaxGenerator.GetGenerator(document)
 
            Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False)
            Dim newRoot = importService.AddImport(compilation, root, contextNode, importsStatement, generator, options, cancellationToken)
            newRoot = newRoot.WithAdditionalAnnotations(CaseCorrector.Annotation, Formatter.Annotation)
            Dim newDocument = document.WithSyntaxRoot(newRoot)
 
            Return newDocument
        End Function
 
        Protected Overrides Function AddImportAsync(
                contextNode As SyntaxNode,
                nameSpaceParts As IReadOnlyList(Of String),
                document As Document,
                options As AddImportPlacementOptions,
                cancellationToken As CancellationToken) As Task(Of Document)
            Dim nameSyntax = CreateNameSyntax(nameSpaceParts, nameSpaceParts.Count - 1)
            Dim importsStatement = GetImportsStatement(nameSyntax)
 
            Return AddImportAsync(contextNode, document, importsStatement, options, cancellationToken)
        End Function
 
        Private Shared Function CreateNameSyntax(nameSpaceParts As IReadOnlyList(Of String), index As Integer) As NameSyntax
            Dim namePiece = SyntaxFactory.IdentifierName(nameSpaceParts(index))
            Return If(index = 0,
                DirectCast(namePiece, NameSyntax),
                SyntaxFactory.QualifiedName(CreateNameSyntax(nameSpaceParts, index - 1), namePiece))
        End Function
 
        Protected Overrides Function IsViableExtensionMethod(method As IMethodSymbol,
                                                             expression As SyntaxNode,
                                                             semanticModel As SemanticModel,
                                                             syntaxFacts As ISyntaxFacts,
                                                             cancellationToken As CancellationToken) As Boolean
            Dim leftExpressionType As ITypeSymbol
            If syntaxFacts.IsInvocationExpression(expression) Then
                leftExpressionType = semanticModel.GetEnclosingNamedType(expression.SpanStart, cancellationToken)
            Else
                Dim leftExpression As SyntaxNode
                If TypeOf expression Is ObjectCreationExpressionSyntax Then
                    leftExpression = expression
                Else
                    leftExpression = syntaxFacts.GetExpressionOfMemberAccessExpression(
                        expression, allowImplicitTarget:=True)
                    If leftExpression Is Nothing Then
                        Return False
                    End If
                End If
 
                Dim semanticInfo = semanticModel.GetTypeInfo(leftExpression, cancellationToken)
                leftExpressionType = semanticInfo.Type
            End If
 
            Return IsViableExtensionMethod(method, leftExpressionType)
        End Function
 
        Protected Overrides Function IsAddMethodContext(node As SyntaxNode, semanticModel As SemanticModel) As Boolean
            If node.IsKind(SyntaxKind.ObjectCollectionInitializer) Then
                Dim objectCreateExpression = node.GetAncestor(Of ObjectCreationExpressionSyntax)
                If objectCreateExpression Is Nothing Then
                    Return False
                End If
 
                Return True
            End If
 
            Return False
        End Function
    End Class
End Namespace