File: ExtractMethod\VisualBasicMethodExtractor.PostProcessor.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 Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod
    Partial Friend NotInheritable Class VisualBasicExtractMethodService
        Partial Friend Class VisualBasicMethodExtractor
            Private Class PostProcessor
                Private ReadOnly _semanticModel As SemanticModel
                Private ReadOnly _contextPosition As Integer
 
                Public Sub New(semanticModel As SemanticModel, contextPosition As Integer)
                    Contract.ThrowIfNull(semanticModel)
 
                    Me._semanticModel = semanticModel
                    Me._contextPosition = contextPosition
                End Sub
 
                Public Function MergeDeclarationStatements(statements As ImmutableArray(Of StatementSyntax)) As ImmutableArray(Of StatementSyntax)
                    If statements.FirstOrDefault() Is Nothing Then
                        Return statements
                    End If
 
                    Return MergeDeclarationStatementsWorker(statements)
                End Function
 
                Private Function MergeDeclarationStatementsWorker(statements As ImmutableArray(Of StatementSyntax)) As ImmutableArray(Of StatementSyntax)
                    Dim declarationStatements = New List(Of StatementSyntax)()
 
                    Dim map = New Dictionary(Of ITypeSymbol, List(Of LocalDeclarationStatementSyntax))()
                    For Each statement In statements
                        If Not IsDeclarationMergable(statement) Then
                            For Each declStatement In GetMergedDeclarationStatements(map)
                                declarationStatements.Add(declStatement)
                            Next declStatement
 
                            declarationStatements.Add(statement)
                            Continue For
                        End If
 
                        AppendDeclarationStatementToMap(TryCast(statement, LocalDeclarationStatementSyntax), map)
                    Next statement
 
                    ' merge leftover
                    If map.Count > 0 Then
                        For Each declStatement In GetMergedDeclarationStatements(map)
                            declarationStatements.Add(declStatement)
                        Next declStatement
                    End If
 
                    Return declarationStatements.ToImmutableArray()
                End Function
 
                Private Sub AppendDeclarationStatementToMap(statement As LocalDeclarationStatementSyntax, map As Dictionary(Of ITypeSymbol, List(Of LocalDeclarationStatementSyntax)))
                    Contract.ThrowIfNull(statement)
                    Contract.ThrowIfFalse(statement.Declarators.Count = 1)
 
                    Dim declarator = statement.Declarators(0)
                    Dim symbolInfo = Me._semanticModel.GetSpeculativeSymbolInfo(Me._contextPosition, declarator.AsClause.Type, SpeculativeBindingOption.BindAsTypeOrNamespace)
                    Dim type = TryCast(symbolInfo.Symbol, ITypeSymbol)
                    Contract.ThrowIfNull(type)
 
                    map.GetOrAdd(type, Function() New List(Of LocalDeclarationStatementSyntax)()).Add(statement)
                End Sub
 
                Private Shared Function GetMergedDeclarationStatements(map As Dictionary(Of ITypeSymbol, List(Of LocalDeclarationStatementSyntax))) As IEnumerable(Of LocalDeclarationStatementSyntax)
                    Dim declarationStatements = New List(Of LocalDeclarationStatementSyntax)()
 
                    For Each keyValuePair In map
                        Contract.ThrowIfFalse(keyValuePair.Value.Count > 0)
 
                        ' merge all variable decl for current type
                        Dim variables = New List(Of ModifiedIdentifierSyntax)()
                        For Each statement In keyValuePair.Value
                            For Each variable In statement.Declarators(0).Names
                                variables.Add(variable)
                            Next variable
                        Next statement
 
                        ' and create one decl statement
                        ' use type name from the first decl statement
                        Dim firstDeclaration = keyValuePair.Value.First()
                        declarationStatements.Add(
                            SyntaxFactory.LocalDeclarationStatement(firstDeclaration.Modifiers,
                                                    SyntaxFactory.SingletonSeparatedList(
                                                        SyntaxFactory.VariableDeclarator(SyntaxFactory.SeparatedList(variables)).WithAsClause(firstDeclaration.Declarators(0).AsClause))
                                                        ))
                    Next keyValuePair
 
                    map.Clear()
 
                    Return declarationStatements
                End Function
 
                Private Function IsDeclarationMergable(statement As StatementSyntax) As Boolean
                    Contract.ThrowIfNull(statement)
 
                    ' to be mergable, statement must be
                    ' 1. decl statement without any extra info
                    ' 2. no initialization on any of its decls
                    ' 3. no trivia except whitespace
                    ' 4. type must be known
 
                    Dim declarationStatement = TryCast(statement, LocalDeclarationStatementSyntax)
                    If declarationStatement Is Nothing Then
                        Return False
                    End If
 
                    If declarationStatement.Modifiers.Any(SyntaxKind.ConstKeyword) OrElse
                       declarationStatement.IsMissing Then
                        Return False
                    End If
 
                    If ContainsAnyInitialization(declarationStatement) Then
                        Return False
                    End If
 
                    If Not ContainsOnlyWhitespaceTrivia(declarationStatement) Then
                        Return False
                    End If
 
                    If declarationStatement.Declarators.Count <> 1 Then
                        Return False
                    End If
 
                    If declarationStatement.Declarators(0).AsClause Is Nothing Then
                        Return False
                    End If
 
                    Dim symbolInfo = Me._semanticModel.GetSpeculativeSymbolInfo(Me._contextPosition, declarationStatement.Declarators(0).AsClause.Type, SpeculativeBindingOption.BindAsTypeOrNamespace)
                    Dim type = TryCast(symbolInfo.Symbol, ITypeSymbol)
                    If type Is Nothing OrElse
                       type.TypeKind = TypeKind.Error OrElse
                       type.TypeKind = TypeKind.Unknown Then
                        Return False
                    End If
 
                    Return True
                End Function
 
                Private Shared Function ContainsAnyInitialization(statement As LocalDeclarationStatementSyntax) As Boolean
                    For Each variable In statement.Declarators
                        If variable.Initializer IsNot Nothing Then
                            Return True
                        End If
                    Next variable
 
                    Return False
                End Function
 
                Private Shared Function ContainsOnlyWhitespaceTrivia(statement As StatementSyntax) As Boolean
                    For Each token In statement.DescendantTokens()
                        If Not ContainsOnlyWhitespaceTrivia(token) Then
                            Return False
                        End If
                    Next token
 
                    Return True
                End Function
 
                Private Shared Function ContainsOnlyWhitespaceTrivia(token As SyntaxToken) As Boolean
                    For Each trivia In token.LeadingTrivia.Concat(token.TrailingTrivia)
                        If trivia.Kind <> SyntaxKind.WhitespaceTrivia AndAlso trivia.Kind <> SyntaxKind.EndOfLineTrivia Then
                            Return False
                        End If
                    Next trivia
 
                    Return True
                End Function
 
                Public Shared Function RemoveDeclarationAssignmentPattern(statements As ImmutableArray(Of StatementSyntax)) As ImmutableArray(Of StatementSyntax)
                    If statements.Count() < 2 Then
                        Return statements
                    End If
 
                    ' if we have inline temp variable as service, we could just use that service here.
                    ' since it is not a service right now, do very simple clean up
                    Dim declaration = TryCast(statements(0), LocalDeclarationStatementSyntax)
                    Dim assignment = TryCast(statements(1), AssignmentStatementSyntax)
                    If declaration Is Nothing OrElse assignment Is Nothing Then
                        Return statements
                    End If
 
                    If ContainsAnyInitialization(declaration) OrElse
                       declaration.Modifiers.Any(Function(m) m.Kind <> SyntaxKind.DimKeyword) OrElse
                       declaration.Declarators.Count <> 1 OrElse
                       declaration.Declarators(0).Names.Count <> 1 OrElse
                       assignment.Left Is Nothing OrElse
                       assignment.Right Is Nothing Then
                        Return statements
                    End If
 
                    If Not ContainsOnlyWhitespaceTrivia(declaration) OrElse
                       Not ContainsOnlyWhitespaceTrivia(assignment) Then
                        Return statements
                    End If
 
                    Dim variableName = declaration.Declarators(0).Names(0).ToString()
 
                    If assignment.Left.ToString() <> variableName Then
                        Return statements
                    End If
 
                    Dim variable = declaration.Declarators(0).WithoutTrailingTrivia().WithInitializer(SyntaxFactory.EqualsValue(assignment.Right))
                    Dim newDeclaration = declaration.WithDeclarators(SyntaxFactory.SingletonSeparatedList(variable))
 
                    Return SpecializedCollections.SingletonEnumerable(Of StatementSyntax)(newDeclaration).Concat(statements.Skip(2)).ToImmutableArray()
                End Function
 
                Public Shared Function RemoveInitializedDeclarationAndReturnPattern(statements As ImmutableArray(Of StatementSyntax)) As ImmutableArray(Of StatementSyntax)
                    ' if we have inline temp variable as service, we could just use that service here.
                    ' since it is not a service right now, do very simple clean up
                    If statements.Count() <> 2 Then
                        Return statements
                    End If
 
                    Dim declaration = TryCast(statements.ElementAtOrDefault(0), LocalDeclarationStatementSyntax)
                    Dim returnStatement = TryCast(statements.ElementAtOrDefault(1), ReturnStatementSyntax)
                    If declaration Is Nothing OrElse returnStatement Is Nothing Then
                        Return statements
                    End If
 
                    If declaration.Declarators.Count <> 1 OrElse
                        declaration.Declarators(0).Names.Count <> 1 OrElse
                        declaration.Declarators(0).Initializer Is Nothing OrElse
                        returnStatement.Expression Is Nothing Then
                        Return statements
                    End If
 
                    If Not ContainsOnlyWhitespaceTrivia(declaration) OrElse
                       Not ContainsOnlyWhitespaceTrivia(returnStatement) Then
                        Return statements
                    End If
 
                    Dim variableName = declaration.Declarators(0).Names(0).ToString()
                    If returnStatement.Expression.ToString() <> variableName Then
                        Return statements
                    End If
 
                    Return SpecializedCollections.SingletonEnumerable(Of StatementSyntax)(
                        SyntaxFactory.ReturnStatement(declaration.Declarators(0).Initializer.Value)).Concat(statements.Skip(2)).ToImmutableArray()
                End Function
            End Class
        End Class
    End Class
End Namespace