File: Completion\KeywordRecommenders\Expressions\BinaryOperatorKeywordRecommender.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.Threading
Imports Microsoft.CodeAnalysis.Completion.Providers
Imports Microsoft.CodeAnalysis.VisualBasic.Extensions.ContextQuery
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.KeywordRecommenders.Expressions
    ''' <summary>
    ''' Recommends binary infix operators that are English text, like "AndAlso", "OrElse", "Like", etc.
    ''' </summary>
    Friend Class BinaryOperatorKeywordRecommender
        Inherits AbstractKeywordRecommender
 
        Friend Shared ReadOnly KeywordList As ImmutableArray(Of RecommendedKeyword) = ImmutableArray.Create(
            New RecommendedKeyword("And", VBFeaturesResources.Performs_a_logical_conjunction_on_two_Boolean_expressions_or_a_bitwise_conjunction_on_two_numeric_expressions_For_Boolean_expressions_returns_True_if_both_operands_evaluate_to_True_Both_expressions_are_always_evaluated_result_expression1_And_expression2),
            New RecommendedKeyword("AndAlso", VBFeaturesResources.Performs_a_short_circuit_logical_conjunction_on_two_expressions_Returns_True_if_both_operands_evaluate_to_True_If_the_first_expression_evaluates_to_False_the_second_is_not_evaluated_result_expression1_AndAlso_expression2),
            New RecommendedKeyword("Or", VBFeaturesResources.Performs_an_inclusive_logical_disjunction_on_two_Boolean_expressions_or_a_bitwise_disjunction_on_two_numeric_expressions_For_Boolean_expressions_returns_True_if_at_least_one_operand_evaluates_to_True_Both_expressions_are_always_evaluated_result_expression1_Or_expression2),
            New RecommendedKeyword("OrElse", VBFeaturesResources.Performs_short_circuit_inclusive_logical_disjunction_on_two_expressions_Returns_True_if_either_operand_evaluates_to_True_If_the_first_expression_evaluates_to_True_the_second_expression_is_not_evaluated_result_expression1_OrElse_expression2),
            New RecommendedKeyword("Is", VBFeaturesResources.Compares_two_object_reference_variables_and_returns_True_if_the_objects_are_equal_result_object1_Is_object2),
            New RecommendedKeyword("IsNot", VBFeaturesResources.Compares_two_object_reference_variables_and_returns_True_if_the_objects_are_not_equal_result_object1_IsNot_object2),
            New RecommendedKeyword("Mod", VBFeaturesResources.Divides_two_numbers_and_returns_only_the_remainder_number1_Mod_number2),
            New RecommendedKeyword("Like", VBFeaturesResources.Compares_a_string_against_a_pattern_Wildcards_available_include_to_match_1_character_and_to_match_0_or_more_characters_result_string_Like_pattern),
            New RecommendedKeyword("Xor", VBFeaturesResources.Performs_a_logical_exclusion_on_two_Boolean_expressions_or_a_bitwise_exclusion_on_two_numeric_expressions_For_Boolean_expressions_returns_True_if_exactly_one_of_the_expressions_evaluates_to_True_Both_expressions_are_always_evaluated_result_expression1_Xor_expression2))
 
        Protected Overrides Function RecommendKeywords(context As VisualBasicSyntaxContext, cancellationToken As CancellationToken) As ImmutableArray(Of RecommendedKeyword)
            If IsBinaryOperatorContext(context, cancellationToken) Then
                Return KeywordList
            End If
 
            Return ImmutableArray(Of RecommendedKeyword).Empty
        End Function
 
        Private Shared Function IsBinaryOperatorContext(context As VisualBasicSyntaxContext, cancellationToken As CancellationToken) As Boolean
            If context.FollowsEndOfStatement Then
                Return False
            End If
 
            Dim token = context.TargetToken
 
            ' Very specific case edge case when the identifier is From or Aggregate. In that case, we'll
            ' only show the binary operator keywords if "From" or "Aggregate" binds to a symbol. In that
            ' way, we can distinguish between the two following cases:
            '
            ' 1.
            ' Dim q = From |
            '
            ' 2.
            ' Dim From = 0
            ' Dim q = From |
 
            Dim identifierName = TryCast(token.Parent, IdentifierNameSyntax)
            If identifierName IsNot Nothing Then
                Dim text = token.ToString()
                If (SyntaxFacts.GetContextualKeywordKind(text) = SyntaxKind.FromKeyword OrElse SyntaxFacts.GetContextualKeywordKind(text) = SyntaxKind.AggregateKeyword) Then
                    Dim symbol = context.SemanticModel.GetSymbolInfo(identifierName, cancellationToken).Symbol
                    If symbol Is Nothing Then
                        Return False
                    End If
                End If
            End If
 
            ' Don't show binary operator keywords in an incomplete Using block
            ' Using goo |
            Dim usingStatement = token.GetAncestor(Of UsingStatementSyntax)()
            If usingStatement IsNot Nothing AndAlso usingStatement.Expression IsNot Nothing AndAlso Not usingStatement.Expression.IsMissing Then
                If usingStatement.Expression Is token.Parent Then
                    Return False
                End If
            End If
 
            ' As a policy, we'll not show them after an object or collection initializer, since we
            ' really just want to show "From" or "With"
            If token.IsFollowingCompleteAsNewClause() OrElse
               token.IsFollowingCompleteObjectCreationInitializer() Then
                Return False
            End If
 
            ' Binary operators are legal inside a join expression, but we'll show
            ' just "Equals" to better guide the user on what they should be
            ' typing
            If context.SyntaxTree.IsFollowingCompleteExpression(Of JoinConditionSyntax)(
               context.Position, context.TargetToken, Function(j) j.Left, cancellationToken) Then
                Return False
            End If
 
            ' Binary operators are allowed in cases like
            '
            '    From num In { 1, 2, 3 } Group By a = num |
            '
            ' but we will choose to exclude them so the user gets better hints of what they have to
            ' type next in the query
            If context.SyntaxTree.IsFollowingCompleteExpression(Of ExpressionRangeVariableSyntax)(
               context.Position, context.TargetToken, Function(j) j.Expression, cancellationToken) Then
                Return False
            End If
 
            ' Some operators (And, Or) are technically legal after an AddressOf expression, but
            ' that's unnecessarily pedantic
            If context.SyntaxTree.IsFollowingCompleteExpression(Of UnaryExpressionSyntax)(context.Position, context.TargetToken,
                Function(u As UnaryExpressionSyntax)
                    If u.Kind = SyntaxKind.AddressOfExpression Then
                        Return u
                    Else
                        Return Nothing
                    End If
                End Function, cancellationToken) Then
 
                Return False
            End If
 
            ' In either of these cases:
            '
            '     Dim x(0 |
            '     ReDim y(0 |
            '
            ' it's legal to write a binary operator, but in all probability the user wants to write
            ' To. Note that if they are writing To then it must be a literal zero, so we'll restrict
            ' to that case
            If token.Kind = SyntaxKind.IntegerLiteralToken AndAlso CInt(token.Value) = 0 Then
                If token.Parent.IsParentKind(SyntaxKind.SimpleArgument) Then
                    Dim argumentList = token.GetAncestor(Of ArgumentListSyntax)()
                    If argumentList.Parent IsNot Nothing AndAlso (TypeOf argumentList.Parent.Parent Is ReDimStatementSyntax OrElse
                                                                  TypeOf argumentList.Parent.Parent Is VariableDeclaratorSyntax) Then
                        Return False
                    End If
                End If
            End If
 
            ' The expression in an Add/RemoveHandler which specifies the event is just an event, and
            ' thus can't get operators applied to it
            If context.SyntaxTree.IsFollowingCompleteExpression(Of AddRemoveHandlerStatementSyntax)(
               context.Position, context.TargetToken, Function(h) h.EventExpression, cancellationToken) Then
                Return False
            End If
 
            ' Exclude from For statements:
            '       For i = 1 |
            ' This is legal but is not a good experience in most cases
            If context.SyntaxTree.IsFollowingCompleteExpression(Of ForStatementSyntax)(context.Position, context.TargetToken, Function(forStatement) forStatement.FromValue, cancellationToken) Then
                Return False
            End If
 
            Return context.SyntaxTree.IsFollowingCompleteExpression(Of ExpressionSyntax)(context.Position, context.TargetToken,
               Function(e)
                   If context.SyntaxTree.IsExpressionContext(e.SpanStart, cancellationToken, context.SemanticModel) Then
                       Return e
                   Else
                       Return Nothing
                   End If
               End Function, cancellationToken)
        End Function
    End Class
End Namespace