File: Completion\CompletionProviders\ObjectInitializerCompletionProvider.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.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.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
    <ExportCompletionProvider(NameOf(ObjectInitializerCompletionProvider), LanguageNames.VisualBasic), [Shared]>
    <ExtensionOrder(After:=NameOf(PreprocessorCompletionProvider))>
    Friend NotInheritable Class ObjectInitializerCompletionProvider
        Inherits AbstractObjectInitializerCompletionProvider
 
        <ImportingConstructor>
        <Obsolete(MefConstruction.ImportingConstructorMessage, True)>
        Public Sub New()
        End Sub
 
        Friend Overrides ReadOnly Property Language As String
            Get
                Return LanguageNames.VisualBasic
            End Get
        End Property
 
        Protected Overrides Function GetInitializedMembers(tree As SyntaxTree, position As Integer, cancellationToken As CancellationToken) As HashSet(Of String)
            Dim token = tree.FindTokenOnLeftOfPosition(position, cancellationToken)
            token = token.GetPreviousTokenIfTouchingWord(position)
 
            ' The dot wasn't part of the identifier, so move over one more to get the , or {
            If token.Kind = SyntaxKind.DotToken Then
                token = token.GetPreviousToken()
            End If
 
            If token.Kind <> SyntaxKind.CommaToken AndAlso token.Kind <> SyntaxKind.OpenBraceToken Then
                Return New HashSet(Of String)
            End If
 
            Dim initializer = TryCast(token.Parent, ObjectMemberInitializerSyntax)
            If initializer Is Nothing Then
                Return New HashSet(Of String)()
            End If
 
            Return New HashSet(Of String)(initializer.Initializers.OfType(Of NamedFieldInitializerSyntax)().Select(Function(i) i.Name.Identifier.ValueText))
        End Function
 
        Protected Overrides Function GetInitializedType(
                document As Document,
                semanticModel As SemanticModel,
                position As Integer,
                cancellationToken As CancellationToken) As Tuple(Of ITypeSymbol, Location)
            Dim tree = semanticModel.SyntaxTree
            If tree.IsInNonUserCode(position, cancellationToken) Then
                Return Nothing
            End If
 
            Dim token = tree.FindTokenOnLeftOfPosition(position, cancellationToken)
            token = token.GetPreviousTokenIfTouchingWord(position)
 
            ' We should have gotten a ".", since that all we want to come up on 
            If token.Kind <> SyntaxKind.DotToken Then
                Return Nothing
            End If
 
            ' The dot must be following a comma or open brace
            Dim commaOrBrace = token.GetPreviousToken()
            If commaOrBrace.Kind <> SyntaxKind.CommaToken AndAlso commaOrBrace.Kind <> SyntaxKind.OpenBraceToken Then
                Return Nothing
            End If
 
            ' We have the right tokens. Get the containing object initializer. Will we be able to walk
            ' up and determine the type?
            Dim containingInitializer = commaOrBrace.Parent
            If containingInitializer Is Nothing OrElse
                containingInitializer.Parent Is Nothing OrElse
                containingInitializer.Kind <> SyntaxKind.ObjectMemberInitializer Then
                Return Nothing
            End If
 
            ' Get the grandparent object creation expression
            Dim objectCreationExpression = TryCast(containingInitializer.Parent, ObjectCreationExpressionSyntax)
            If objectCreationExpression Is Nothing Then
                Return Nothing
            End If
 
            Dim initializerLocation As Location = token.GetLocation()
            Dim symbolInfo = semanticModel.GetSymbolInfo(objectCreationExpression.Type, cancellationToken)
            Dim symbol = TryCast(symbolInfo.Symbol, ITypeSymbol)
            Return Tuple.Create(symbol, initializerLocation)
        End Function
 
        Public Overrides Function IsInsertionTrigger(text As SourceText, characterPosition As Integer, options As CompletionOptions) As Boolean
            Return text(characterPosition) = "."c
        End Function
 
        Public Overrides ReadOnly Property TriggerCharacters As ImmutableHashSet(Of Char) = ImmutableHashSet.Create("."c)
 
        Protected Overrides Function IsExclusiveAsync(document As Document, position As Integer, cancellationToken As CancellationToken) As Task(Of Boolean)
            ' Object initializers are explicitly indicated by "With", so we're always exclusive.
            Return SpecializedTasks.True
        End Function
 
        Protected Overrides Function IsInitializableFieldOrProperty(fieldOrProperty As ISymbol, containingType As INamedTypeSymbol) As Boolean
            ' Unlike CSharp, we don't want to suggest readonly members, even if they are Collections
            Return MyBase.IsInitializableFieldOrProperty(fieldOrProperty, containingType) AndAlso
                fieldOrProperty.IsWriteableFieldOrProperty() AndAlso
                IsValidProperty(fieldOrProperty)
        End Function
 
        Protected Overrides Function EscapeIdentifier(symbol As ISymbol) As String
            Return symbol.Name.EscapeIdentifier()
        End Function
 
        Private Shared Function IsValidProperty(member As ISymbol) As Boolean
            Dim [property] = TryCast(member, IPropertySymbol)
            If [property] IsNot Nothing Then
                Return [property].Parameters.IsDefaultOrEmpty OrElse [property].Parameters.All(Function(p) p.IsOptional OrElse p.IsParams)
            End If
 
            Return True
        End Function
    End Class
End Namespace