|
' 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.Composition
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Completion
Imports Microsoft.CodeAnalysis.Completion.Providers
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.Options
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Extensions.ContextQuery
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
<ExportCompletionProvider(NameOf(ImplementsClauseCompletionProvider), LanguageNames.VisualBasic)>
<ExtensionOrder(After:=NameOf(VisualBasicSuggestionModeCompletionProvider))>
<[Shared]>
Partial Friend Class ImplementsClauseCompletionProvider
Inherits AbstractSymbolCompletionProvider(Of VisualBasicSyntaxContext)
<ImportingConstructor>
<Obsolete(MefConstruction.ImportingConstructorMessage, True)>
Public Sub New()
End Sub
Public Overrides Function IsInsertionTrigger(text As SourceText, characterPosition As Integer, options As CompletionOptions) As Boolean
Return CompletionUtilities.IsDefaultTriggerCharacter(text, characterPosition, options)
End Function
Public Overrides ReadOnly Property TriggerCharacters As ImmutableHashSet(Of Char) = CompletionUtilities.CommonTriggerChars
Protected Overrides Function IsExclusive() As Boolean
Return True
End Function
Friend Overrides ReadOnly Property Language As String
Get
Return LanguageNames.VisualBasic
End Get
End Property
Protected Overrides Async Function GetSymbolsAsync(
completionContext As CompletionContext,
syntaxContext As VisualBasicSyntaxContext,
position As Integer,
options As CompletionOptions,
cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of SymbolAndSelectionInfo))
Dim symbols = Await GetSymbolsAsync(syntaxContext, position, cancellationToken).ConfigureAwait(False)
Return symbols.SelectAsArray(Function(s) New SymbolAndSelectionInfo(Symbol:=s, Preselect:=False))
End Function
Private Overloads Function GetSymbolsAsync(
context As VisualBasicSyntaxContext, position As Integer, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of ISymbol))
If context.TargetToken.Kind = SyntaxKind.None Then
Return SpecializedTasks.EmptyImmutableArray(Of ISymbol)()
End If
If context.SyntaxTree.IsInNonUserCode(position, cancellationToken) OrElse
context.SyntaxTree.IsInSkippedText(position, cancellationToken) Then
Return SpecializedTasks.EmptyImmutableArray(Of ISymbol)()
End If
' We only care about Methods, Properties, and Events
Dim memberKindKeyword As SyntaxKind = Nothing
Dim methodDeclaration = context.TargetToken.GetAncestor(Of MethodStatementSyntax)()
If methodDeclaration IsNot Nothing Then
memberKindKeyword = methodDeclaration.DeclarationKeyword.Kind
End If
Dim propertyDeclaration = context.TargetToken.GetAncestor(Of PropertyStatementSyntax)()
If propertyDeclaration IsNot Nothing Then
memberKindKeyword = propertyDeclaration.DeclarationKeyword.Kind
End If
Dim eventDeclaration = context.TargetToken.GetAncestor(Of EventStatementSyntax)()
If eventDeclaration IsNot Nothing Then
memberKindKeyword = eventDeclaration.DeclarationKeyword.Kind
End If
' We couldn't find a declaration. Bail.
If memberKindKeyword = Nothing Then
Return SpecializedTasks.EmptyImmutableArray(Of ISymbol)()
End If
Dim result = ImmutableArray(Of ISymbol).Empty
' Valid positions: Immediately after 'Implements, after ., or after a ,
If context.TargetToken.Kind = SyntaxKind.ImplementsKeyword AndAlso context.TargetToken.Parent.IsKind(SyntaxKind.ImplementsClause) Then
result = GetInterfacesAndContainers(position, context.TargetToken.Parent, context.SemanticModel, memberKindKeyword, cancellationToken)
End If
If context.TargetToken.Kind = SyntaxKind.CommaToken AndAlso context.TargetToken.Parent.IsKind(SyntaxKind.ImplementsClause) Then
result = GetInterfacesAndContainers(position, context.TargetToken.Parent, context.SemanticModel, memberKindKeyword, cancellationToken)
End If
If context.TargetToken.IsKindOrHasMatchingText(SyntaxKind.DotToken) AndAlso WalkUpQualifiedNames(context.TargetToken) Then
result = GetDottedMembers(position, DirectCast(context.TargetToken.Parent, QualifiedNameSyntax), context.SemanticModel, memberKindKeyword, cancellationToken)
End If
If result.Length > 0 Then
Return Task.FromResult(result.WhereAsArray(Function(s) MatchesMemberKind(s, memberKindKeyword)))
End If
Return SpecializedTasks.EmptyImmutableArray(Of ISymbol)()
End Function
Private Shared Function MatchesMemberKind(symbol As ISymbol, memberKindKeyword As SyntaxKind) As Boolean
If symbol.Kind = SymbolKind.Alias Then
symbol = DirectCast(symbol, IAliasSymbol).Target
End If
If TypeOf symbol Is INamespaceOrTypeSymbol Then
Return True
End If
Dim method = TryCast(symbol, IMethodSymbol)
If method IsNot Nothing Then
If Not method.ReturnsVoid Then
Return memberKindKeyword = SyntaxKind.FunctionKeyword
End If
Return memberKindKeyword = SyntaxKind.SubKeyword
End If
Dim [property] = TryCast(symbol, IPropertySymbol)
If [property] IsNot Nothing Then
Return memberKindKeyword = SyntaxKind.PropertyKeyword
End If
Return memberKindKeyword = SyntaxKind.EventKeyword
End Function
Private Shared Function GetDottedMembers(position As Integer, qualifiedName As QualifiedNameSyntax, semanticModel As SemanticModel, memberKindKeyword As SyntaxKind, cancellationToken As CancellationToken) As ImmutableArray(Of ISymbol)
Dim containingType = semanticModel.GetEnclosingNamedType(position, cancellationToken)
If containingType Is Nothing Then
Return ImmutableArray(Of ISymbol).Empty
End If
Dim unimplementedInterfacesAndMembers = From item In containingType.GetAllUnimplementedMembersInThis(containingType.Interfaces, cancellationToken)
Select New With {.interface = item.Item1, .members = item.Item2.Where(Function(s) MatchesMemberKind(s, memberKindKeyword))}
Dim interfaces = unimplementedInterfacesAndMembers.Where(Function(i) i.members.Any()) _
.Select(Function(i) i.interface)
Dim members = unimplementedInterfacesAndMembers.SelectMany(Function(i) i.members)
Dim interfacesAndContainers = New HashSet(Of ISymbol)(interfaces)
For Each [interface] In interfaces
AddAliasesAndContainers([interface], interfacesAndContainers, Nothing, Nothing)
Next
Dim namespaces = interfacesAndContainers.OfType(Of INamespaceSymbol)()
Dim left = qualifiedName.Left
Dim leftHandTypeInfo = semanticModel.GetTypeInfo(left, cancellationToken)
Dim leftHandBinding = semanticModel.GetSymbolInfo(left, cancellationToken)
Dim container As INamespaceOrTypeSymbol = leftHandTypeInfo.Type
If container Is Nothing OrElse container.IsErrorType Then
container = TryCast(leftHandBinding.Symbol, INamespaceOrTypeSymbol)
End If
If container Is Nothing Then
container = TryCast(leftHandBinding.CandidateSymbols.FirstOrDefault(), INamespaceOrTypeSymbol)
End If
If container Is Nothing Then
Return ImmutableArray(Of ISymbol).Empty
End If
Dim symbols = semanticModel.LookupSymbols(position, container)
Dim hashSet = New HashSet(Of ISymbol)(symbols.ToArray() _
.Where(Function(s As ISymbol) interfacesAndContainers.Contains(s, SymbolEquivalenceComparer.Instance) OrElse
(TypeOf (s) Is INamespaceSymbol AndAlso namespaces.Contains(TryCast(s, INamespaceSymbol), INamespaceSymbolExtensions.EqualityComparer)) OrElse
members.Contains(s)))
Return hashSet.ToImmutableArray()
End Function
Private Function interfaceMemberGetter([interface] As ITypeSymbol, within As ISymbol) As ImmutableArray(Of ISymbol)
Return ImmutableArray.CreateRange(Of ISymbol)([interface].AllInterfaces.SelectMany(Function(i) i.GetMembers()).Where(Function(s) s.IsAccessibleWithin(within))) _
.AddRange([interface].GetMembers())
End Function
Private Function GetInterfacesAndContainers(position As Integer, node As SyntaxNode, semanticModel As SemanticModel, kind As SyntaxKind, cancellationToken As CancellationToken) As ImmutableArray(Of ISymbol)
Dim containingType = semanticModel.GetEnclosingNamedType(position, cancellationToken)
If containingType Is Nothing Then
Return ImmutableArray(Of ISymbol).Empty
End If
Dim interfaceWithUnimplementedMembers = containingType.GetAllUnimplementedMembersInThis(containingType.Interfaces, AddressOf interfaceMemberGetter, cancellationToken) _
.Where(Function(i) i.Item2.Any(Function(interfaceOrContainer) MatchesMemberKind(interfaceOrContainer, kind))) _
.Select(Function(i) i.Item1)
Dim interfacesAndContainers = New HashSet(Of ISymbol)(interfaceWithUnimplementedMembers)
For Each i In interfaceWithUnimplementedMembers
AddAliasesAndContainers(i, interfacesAndContainers, node, semanticModel)
Next
Dim symbols = New HashSet(Of ISymbol)(semanticModel.LookupSymbols(position))
Dim availableInterfacesAndContainers = interfacesAndContainers.Where(
Function(interfaceOrContainer) symbols.Contains(interfaceOrContainer.OriginalDefinition)).ToImmutableArray()
Dim result = TryAddGlobalTo(availableInterfacesAndContainers)
' Even if there's not anything left to implement, we'll show the list of interfaces,
' the global namespace, and the project root namespace (if any), as long as the class implements something.
If Not result.Any() AndAlso containingType.Interfaces.Any() Then
Dim defaultListing = New List(Of ISymbol)(containingType.Interfaces)
defaultListing.Add(semanticModel.Compilation.GlobalNamespace)
If containingType.ContainingNamespace IsNot Nothing Then
defaultListing.Add(containingType.ContainingNamespace)
AddAliasesAndContainers(containingType.ContainingNamespace, defaultListing, node, semanticModel)
End If
Return defaultListing.ToImmutableArray()
End If
Return result
End Function
Private Shared Sub AddAliasesAndContainers(symbol As ISymbol, interfacesAndContainers As ICollection(Of ISymbol), node As SyntaxNode, semanticModel As SemanticModel)
' Add aliases, if any for 'symbol'
AddAlias(symbol, interfacesAndContainers, node, semanticModel)
' Add containers for 'symbol'
Dim containingSymbol = symbol.ContainingSymbol
If containingSymbol IsNot Nothing AndAlso Not interfacesAndContainers.Contains(containingSymbol) Then
interfacesAndContainers.Add(containingSymbol)
' Add aliases, if any for 'containingSymbol'
AddAlias(containingSymbol, interfacesAndContainers, node, semanticModel)
If Not IsGlobal(containingSymbol) Then
AddAliasesAndContainers(containingSymbol, interfacesAndContainers, node, semanticModel)
End If
End If
End Sub
Private Shared Sub AddAlias(symbol As ISymbol, interfacesAndContainers As ICollection(Of ISymbol), node As SyntaxNode, semanticModel As SemanticModel)
If node IsNot Nothing AndAlso semanticModel IsNot Nothing AndAlso TypeOf symbol Is INamespaceOrTypeSymbol Then
Dim aliasSymbol = DirectCast(symbol, INamespaceOrTypeSymbol).GetAliasForSymbol(node, semanticModel)
If aliasSymbol IsNot Nothing AndAlso Not interfacesAndContainers.Contains(aliasSymbol) Then
interfacesAndContainers.Add(aliasSymbol)
End If
End If
End Sub
Private Shared Function IsGlobal(containingSymbol As ISymbol) As Boolean
Dim [namespace] = TryCast(containingSymbol, INamespaceSymbol)
Return [namespace] IsNot Nothing AndAlso [namespace].IsGlobalNamespace
End Function
Private Shared Function TryAddGlobalTo(symbols As ImmutableArray(Of ISymbol)) As ImmutableArray(Of ISymbol)
Dim withGlobalContainer = symbols.FirstOrDefault(Function(s) s.ContainingNamespace.IsGlobalNamespace)
If withGlobalContainer IsNot Nothing Then
Return symbols.Concat(ImmutableArray.Create(Of ISymbol)(withGlobalContainer.ContainingNamespace))
End If
Return symbols
End Function
Private Shared Function WalkUpQualifiedNames(token As SyntaxToken) As Boolean
Dim parent = token.Parent
While parent IsNot Nothing AndAlso parent.IsKind(SyntaxKind.QualifiedName)
parent = parent.Parent
End While
Return parent IsNot Nothing AndAlso parent.IsKind(SyntaxKind.ImplementsClause)
End Function
Protected Overrides Function GetDisplayAndSuffixAndInsertionText(symbol As ISymbol, context As VisualBasicSyntaxContext) As (displayText As String, suffix As String, insertionText As String)
If IsGlobal(symbol) Then
Return ("Global", "", "Global")
End If
If IsGenericType(symbol) Then
Dim displayText = symbol.ToMinimalDisplayString(context.SemanticModel, context.Position)
Return (displayText, "", displayText)
Else
Return CompletionUtilities.GetDisplayAndSuffixAndInsertionText(symbol, context)
End If
End Function
Private Shared Function IsGenericType(symbol As ISymbol) As Boolean
Return symbol.MatchesKind(SymbolKind.NamedType) AndAlso symbol.GetAllTypeArguments().Any()
End Function
Private Shared ReadOnly MinimalFormatWithoutGenerics As SymbolDisplayFormat =
SymbolDisplayFormat.MinimallyQualifiedFormat.WithGenericsOptions(SymbolDisplayGenericsOptions.None)
Private Const InsertionTextOnOpenParen As String = NameOf(InsertionTextOnOpenParen)
Protected Overrides Function CreateItem(
completionContext As CompletionContext,
displayText As String,
displayTextSuffix As String,
insertionText As String,
symbols As ImmutableArray(Of SymbolAndSelectionInfo),
context As VisualBasicSyntaxContext,
supportedPlatformData As SupportedPlatformData) As CompletionItem
Dim item = CreateItemDefault(displayText, displayTextSuffix, insertionText, symbols, context, supportedPlatformData)
If IsGenericType(symbols(0).Symbol) Then
Dim text = symbols(0).Symbol.ToMinimalDisplayString(context.SemanticModel, context.Position, MinimalFormatWithoutGenerics)
item = item.AddProperty(InsertionTextOnOpenParen, text)
End If
Return item
End Function
Protected Overrides Function GetInsertionText(item As CompletionItem, ch As Char) As String
If ch = "("c Then
Dim insertionText As String = Nothing
If item.TryGetProperty(InsertionTextOnOpenParen, insertionText) Then
Return insertionText
End If
End If
Return CompletionUtilities.GetInsertionTextAtInsertionTime(item, ch)
End Function
End Class
End Namespace
|