|
' 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
Imports Microsoft.CodeAnalysis.FindSymbols
Imports Microsoft.CodeAnalysis.LanguageService
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Rename
Imports Microsoft.CodeAnalysis.Rename.ConflictEngine
Imports Microsoft.CodeAnalysis.Simplification
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Simplification
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis.VisualBasic.Utilities
Namespace Microsoft.CodeAnalysis.VisualBasic.Rename
Friend Class VisualBasicRenameRewriterLanguageService
Inherits AbstractRenameRewriterLanguageService
Public Shared ReadOnly Instance As New VisualBasicRenameRewriterLanguageService()
Private Sub New()
End Sub
#Region "Annotate"
Public Overrides Function AnnotateAndRename(parameters As RenameRewriterParameters) As SyntaxNode
Dim renameRewriter = New RenameRewriter(parameters)
Return renameRewriter.Visit(parameters.SyntaxRoot)
End Function
Private Class RenameRewriter
Inherits VisualBasicSyntaxRewriter
Private ReadOnly _documentId As DocumentId
Private ReadOnly _renameRenamableSymbolDeclaration As RenameAnnotation
Private ReadOnly _solution As Solution
Private ReadOnly _replacementText As String
Private ReadOnly _originalText As String
Private ReadOnly _possibleNameConflicts As ImmutableArray(Of String)
Private ReadOnly _renameLocations As ImmutableDictionary(Of TextSpan, RenameLocation)
Private ReadOnly _conflictLocations As ImmutableHashSet(Of TextSpan)
Private ReadOnly _semanticModel As SemanticModel
Private ReadOnly _cancellationToken As CancellationToken
Private ReadOnly _renamedSymbol As ISymbol
Private ReadOnly _aliasSymbol As IAliasSymbol
Private ReadOnly _renamableDeclarationLocation As Location
Private ReadOnly _renameSpansTracker As RenamedSpansTracker
Private ReadOnly _isVerbatim As Boolean
Private ReadOnly _replacementTextValid As Boolean
Private ReadOnly _simplificationService As ISimplificationService
Private ReadOnly _annotatedIdentifierTokens As New HashSet(Of SyntaxToken)
Private ReadOnly _invocationExpressionsNeedingConflictChecks As New HashSet(Of InvocationExpressionSyntax)
Private ReadOnly _syntaxFactsService As ISyntaxFactsService
Private ReadOnly _semanticFactsService As ISemanticFactsService
Private ReadOnly _renameAnnotations As AnnotationTable(Of RenameAnnotation)
''' <summary>
''' Flag indicating if we should perform a rename inside string literals.
''' </summary>
Private ReadOnly _isRenamingInStrings As Boolean
''' <summary>
''' Flag indicating if we should perform a rename inside comment trivia.
''' </summary>
Private ReadOnly _isRenamingInComments As Boolean
''' <summary>
''' A map from spans of tokens needing rename within strings or comments to an optional
''' set of specific sub-spans within the token span that
''' have <see cref="_originalText"/> matches and should be renamed.
''' If this sorted set is Nothing, it indicates that sub-spans to rename within the token span
''' are not available, and a regex match should be performed to rename
''' all <see cref="_originalText"/> matches within the span.
''' </summary>
Private ReadOnly _stringAndCommentTextSpans As ImmutableDictionary(Of TextSpan, ImmutableSortedSet(Of TextSpan))
Private ReadOnly Property AnnotateForComplexification As Boolean
Get
Return Me._skipRenameForComplexification > 0 AndAlso Not Me._isProcessingComplexifiedSpans
End Get
End Property
Private _skipRenameForComplexification As Integer
Private _isProcessingComplexifiedSpans As Boolean
Private _modifiedSubSpans As List(Of ValueTuple(Of TextSpan, TextSpan))
Private _speculativeModel As SemanticModel
Private _isProcessingStructuredTrivia As Integer
Private ReadOnly _complexifiedSpans As HashSet(Of TextSpan) = New HashSet(Of TextSpan)
Private Sub AddModifiedSpan(oldSpan As TextSpan, newSpan As TextSpan)
newSpan = New TextSpan(oldSpan.Start, newSpan.Length)
If Not Me._isProcessingComplexifiedSpans Then
_renameSpansTracker.AddModifiedSpan(_documentId, oldSpan, newSpan)
Else
Me._modifiedSubSpans.Add((oldSpan, newSpan))
End If
End Sub
Public Sub New(parameters As RenameRewriterParameters)
MyBase.New(visitIntoStructuredTrivia:=True)
_documentId = parameters.Document.Id
_renameRenamableSymbolDeclaration = parameters.RenamedSymbolDeclarationAnnotation
_solution = parameters.OriginalSolution
_replacementText = parameters.ReplacementText
_originalText = parameters.OriginalText
_possibleNameConflicts = parameters.PossibleNameConflicts
_renameLocations = parameters.RenameLocations
_conflictLocations = parameters.ConflictLocationSpans
_cancellationToken = parameters.CancellationToken
_semanticModel = parameters.SemanticModel
_renamedSymbol = parameters.RenameSymbol
_replacementTextValid = parameters.ReplacementTextValid
_renameSpansTracker = parameters.RenameSpansTracker
_isRenamingInStrings = parameters.IsRenamingInStrings
_isRenamingInComments = parameters.IsRenamingInComments
_stringAndCommentTextSpans = parameters.StringAndCommentTextSpans
_aliasSymbol = TryCast(_renamedSymbol, IAliasSymbol)
_renamableDeclarationLocation = _renamedSymbol.Locations.Where(Function(loc) loc.IsInSource AndAlso loc.SourceTree Is _semanticModel.SyntaxTree).FirstOrDefault()
_simplificationService = parameters.Document.Project.Services.GetRequiredService(Of ISimplificationService)()
_syntaxFactsService = parameters.Document.Project.Services.GetRequiredService(Of ISyntaxFactsService)()
_semanticFactsService = parameters.Document.Project.Services.GetRequiredService(Of ISemanticFactsService)()
_isVerbatim = _syntaxFactsService.IsVerbatimIdentifier(_replacementText)
_renameAnnotations = parameters.RenameAnnotations
End Sub
Public Overrides Function Visit(node As SyntaxNode) As SyntaxNode
If node Is Nothing Then
Return node
End If
Dim isInConflictLambdaBody = False
Dim lambdas = node.GetAncestorsOrThis(Of MultiLineLambdaExpressionSyntax)()
If lambdas.Count() <> 0 Then
For Each lambda In lambdas
If Me._conflictLocations.Any(Function(cf)
Return cf.Contains(lambda.Span)
End Function) Then
isInConflictLambdaBody = True
Exit For
End If
Next
End If
Dim shouldComplexifyNode = Me.ShouldComplexifyNode(node, isInConflictLambdaBody)
Dim result As SyntaxNode
If shouldComplexifyNode Then
Me._skipRenameForComplexification += 1
result = MyBase.Visit(node)
Me._skipRenameForComplexification -= 1
result = Complexify(node, result)
Else
result = MyBase.Visit(node)
End If
Return result
End Function
Private Function ShouldComplexifyNode(node As SyntaxNode, isInConflictLambdaBody As Boolean) As Boolean
Return Not isInConflictLambdaBody AndAlso
_skipRenameForComplexification = 0 AndAlso
Not _isProcessingComplexifiedSpans AndAlso
_conflictLocations.Contains(node.Span) AndAlso
(TypeOf node Is ExpressionSyntax OrElse
TypeOf node Is StatementSyntax OrElse
TypeOf node Is AttributeSyntax OrElse
TypeOf node Is SimpleArgumentSyntax OrElse
TypeOf node Is CrefReferenceSyntax OrElse
TypeOf node Is TypeConstraintSyntax)
End Function
Private Function Complexify(originalNode As SyntaxNode, newNode As SyntaxNode) As SyntaxNode
If Me._complexifiedSpans.Contains(originalNode.Span) Then
Return newNode
Else
Me._complexifiedSpans.Add(originalNode.Span)
End If
Me._isProcessingComplexifiedSpans = True
Me._modifiedSubSpans = New List(Of ValueTuple(Of TextSpan, TextSpan))()
Dim annotation = New SyntaxAnnotation()
newNode = newNode.WithAdditionalAnnotations(annotation)
Dim speculativeTree = originalNode.SyntaxTree.GetRoot(_cancellationToken).ReplaceNode(originalNode, newNode)
newNode = speculativeTree.GetAnnotatedNodes(Of SyntaxNode)(annotation).First()
Me._speculativeModel = GetSemanticModelForNode(newNode, Me._semanticModel)
Debug.Assert(_speculativeModel IsNot Nothing, "expanding a syntax node which cannot be speculated?")
' There are cases when we change the type of node to make speculation work (e.g.,
' for AsNewClauseSyntax), so getting the newNode from the _speculativeModel
' ensures the final node replacing the original node is found.
newNode = Me._speculativeModel.SyntaxTree.GetRoot(_cancellationToken).GetAnnotatedNodes(Of SyntaxNode)(annotation).First()
Dim oldSpan = originalNode.Span
Dim expandParameter = originalNode.GetAncestorsOrThis(Of LambdaExpressionSyntax).Count() = 0
Dim expandedNewNode = DirectCast(_simplificationService.Expand(newNode,
_speculativeModel,
annotationForReplacedAliasIdentifier:=Nothing,
expandInsideNode:=AddressOf IsExpandWithinMultiLineLambda,
expandParameter:=expandParameter,
cancellationToken:=_cancellationToken), SyntaxNode)
Dim annotationForSpeculativeNode = New SyntaxAnnotation()
expandedNewNode = expandedNewNode.WithAdditionalAnnotations(annotationForSpeculativeNode)
speculativeTree = originalNode.SyntaxTree.GetRoot(_cancellationToken).ReplaceNode(originalNode, expandedNewNode)
Dim probableRenameNode = speculativeTree.GetAnnotatedNodes(Of SyntaxNode)(annotation).First()
Dim speculativeNewNode = speculativeTree.GetAnnotatedNodes(Of SyntaxNode)(annotationForSpeculativeNode).First()
Me._speculativeModel = GetSemanticModelForNode(speculativeNewNode, Me._semanticModel)
Debug.Assert(_speculativeModel IsNot Nothing, "expanding a syntax node which cannot be speculated?")
' There are cases when we change the type of node to make speculation work (e.g.,
' for AsNewClauseSyntax), so getting the newNode from the _speculativeModel
' ensures the final node replacing the original node is found.
probableRenameNode = Me._speculativeModel.SyntaxTree.GetRoot(_cancellationToken).GetAnnotatedNodes(Of SyntaxNode)(annotation).First()
speculativeNewNode = Me._speculativeModel.SyntaxTree.GetRoot(_cancellationToken).GetAnnotatedNodes(Of SyntaxNode)(annotationForSpeculativeNode).First()
Dim renamedNode = MyBase.Visit(probableRenameNode)
If Not ReferenceEquals(renamedNode, probableRenameNode) Then
renamedNode = renamedNode.WithoutAnnotations(annotation)
probableRenameNode = expandedNewNode.GetAnnotatedNodes(Of SyntaxNode)(annotation).First()
expandedNewNode = expandedNewNode.ReplaceNode(probableRenameNode, renamedNode)
End If
Dim newSpan = expandedNewNode.Span
probableRenameNode = probableRenameNode.WithoutAnnotations(annotation)
expandedNewNode = Me._renameAnnotations.WithAdditionalAnnotations(expandedNewNode, New RenameNodeSimplificationAnnotation() With {.OriginalTextSpan = oldSpan})
Me._renameSpansTracker.AddComplexifiedSpan(Me._documentId, oldSpan, New TextSpan(oldSpan.Start, newSpan.Length), Me._modifiedSubSpans)
Me._modifiedSubSpans = Nothing
Me._isProcessingComplexifiedSpans = False
Me._speculativeModel = Nothing
Return expandedNewNode
End Function
Private Function IsExpandWithinMultiLineLambda(node As SyntaxNode) As Boolean
If node Is Nothing Then
Return False
End If
If Me._conflictLocations.Contains(node.Span) Then
Return True
End If
If node.IsParentKind(SyntaxKind.MultiLineSubLambdaExpression) OrElse
node.IsParentKind(SyntaxKind.MultiLineFunctionLambdaExpression) Then
Dim parent = DirectCast(node.Parent, MultiLineLambdaExpressionSyntax)
If ReferenceEquals(parent.SubOrFunctionHeader, node) Then
Return True
Else
Return False
End If
End If
Return True
End Function
Private Shared Function IsPossibleNameConflict(possibleNameConflicts As ICollection(Of String), candidate As String) As Boolean
For Each possibleNameConflict In possibleNameConflicts
If CaseInsensitiveComparison.Equals(possibleNameConflict, candidate) Then
Return True
End If
Next
Return False
End Function
Private Function UpdateAliasAnnotation(newToken As SyntaxToken) As SyntaxToken
If Me._aliasSymbol IsNot Nothing AndAlso Not Me.AnnotateForComplexification AndAlso newToken.HasAnnotations(AliasAnnotation.Kind) Then
newToken = RenameUtilities.UpdateAliasAnnotation(newToken, Me._aliasSymbol, Me._replacementText)
End If
Return newToken
End Function
Private Async Function RenameAndAnnotateAsync(token As SyntaxToken, newToken As SyntaxToken, isRenameLocation As Boolean, isOldText As Boolean) As Task(Of SyntaxToken)
If newToken.IsKind(SyntaxKind.NewKeyword) Then
' The constructor definition cannot be renamed in Visual Basic
Return newToken
End If
If Me._isProcessingComplexifiedSpans Then
If isRenameLocation Then
Dim annotation = Me._renameAnnotations.GetAnnotations(Of RenameActionAnnotation)(token).FirstOrDefault()
If annotation IsNot Nothing Then
newToken = RenameToken(token, newToken, annotation.Prefix, annotation.Suffix)
AddModifiedSpan(annotation.OriginalSpan, New TextSpan(token.Span.Start, newToken.Span.Length))
Else
newToken = RenameToken(token, newToken, prefix:=Nothing, suffix:=Nothing)
End If
End If
Return newToken
End If
Dim symbols = RenameUtilities.GetSymbolsTouchingPosition(token.Span.Start, _semanticModel, _solution.Services, _cancellationToken)
' this is the compiler generated backing field of a non custom event. We need to store a "Event" suffix to properly rename it later on.
Dim prefix = If(isRenameLocation AndAlso Me._renameLocations(token.Span).IsRenamableAccessor, newToken.ValueText.Substring(0, newToken.ValueText.IndexOf("_"c) + 1), String.Empty)
Dim suffix As String = Nothing
If symbols.Length = 1 Then
Dim symbol = symbols(0)
If symbol.IsConstructor() Then
symbol = symbol.ContainingSymbol
End If
If symbol.Kind = SymbolKind.Field AndAlso symbol.IsImplicitlyDeclared Then
Dim fieldSymbol = DirectCast(symbol, IFieldSymbol)
If fieldSymbol.Type.IsDelegateType AndAlso
fieldSymbol.Type.IsImplicitlyDeclared AndAlso
DirectCast(fieldSymbol.Type, INamedTypeSymbol).AssociatedSymbol IsNot Nothing Then
suffix = "Event"
End If
If fieldSymbol.AssociatedSymbol IsNot Nothing AndAlso
fieldSymbol.AssociatedSymbol.IsKind(SymbolKind.Property) AndAlso
fieldSymbol.Name = "_" + fieldSymbol.AssociatedSymbol.Name Then
prefix = "_"
End If
ElseIf symbol.IsConstructor AndAlso
symbol.ContainingType.IsImplicitlyDeclared AndAlso
symbol.ContainingType.IsDelegateType AndAlso
symbol.ContainingType.AssociatedSymbol IsNot Nothing Then
suffix = "EventHandler"
ElseIf TypeOf symbol Is INamedTypeSymbol Then
Dim namedTypeSymbol = DirectCast(symbol, INamedTypeSymbol)
If namedTypeSymbol.IsImplicitlyDeclared AndAlso
namedTypeSymbol.IsDelegateType() AndAlso
namedTypeSymbol.AssociatedSymbol IsNot Nothing Then
suffix = "EventHandler"
End If
End If
If Not isRenameLocation AndAlso TypeOf (symbol) Is INamespaceSymbol AndAlso token.GetPreviousToken().Kind = SyntaxKind.NamespaceKeyword Then
Return newToken
End If
End If
If isRenameLocation AndAlso Not Me.AnnotateForComplexification Then
Dim oldSpan = token.Span
newToken = RenameToken(token, newToken, prefix:=prefix, suffix:=suffix)
AddModifiedSpan(oldSpan, newToken.Span)
End If
Dim renameDeclarationLocations As RenameDeclarationLocationReference() =
Await ConflictResolver.CreateDeclarationLocationAnnotationsAsync(_solution, symbols, _cancellationToken).ConfigureAwait(False)
Dim isNamespaceDeclarationReference = False
If isRenameLocation AndAlso token.GetPreviousToken().Kind = SyntaxKind.NamespaceKeyword Then
isNamespaceDeclarationReference = True
End If
Dim isMemberGroupReference = _semanticFactsService.IsInsideNameOfExpression(_semanticModel, token.Parent, _cancellationToken)
Dim renameAnnotation = New RenameActionAnnotation(
token.Span,
isRenameLocation,
prefix,
suffix,
isOldText,
renameDeclarationLocations,
isNamespaceDeclarationReference,
isInvocationExpression:=False,
isMemberGroupReference:=isMemberGroupReference)
_annotatedIdentifierTokens.Add(token)
newToken = Me._renameAnnotations.WithAdditionalAnnotations(newToken, renameAnnotation, New RenameTokenSimplificationAnnotation() With {.OriginalTextSpan = token.Span})
If Me._renameRenamableSymbolDeclaration IsNot Nothing AndAlso _renamableDeclarationLocation = token.GetLocation() Then
newToken = Me._renameAnnotations.WithAdditionalAnnotations(newToken, Me._renameRenamableSymbolDeclaration)
End If
Return newToken
End Function
Private Function IsInRenameLocation(token As SyntaxToken) As Boolean
If Not Me._isProcessingComplexifiedSpans Then
Return Me._renameLocations.ContainsKey(token.Span)
Else
If token.HasAnnotations(AliasAnnotation.Kind) Then
Return False
End If
If Me._renameAnnotations.HasAnnotations(Of RenameActionAnnotation)(token) Then
Return Me._renameAnnotations.GetAnnotations(Of RenameActionAnnotation)(token).First().IsRenameLocation
End If
If TypeOf token.Parent Is SimpleNameSyntax AndAlso token.Kind <> SyntaxKind.GlobalKeyword AndAlso token.Parent.Parent.IsKind(SyntaxKind.QualifiedName, SyntaxKind.QualifiedCrefOperatorReference) Then
Dim symbol = Me._speculativeModel.GetSymbolInfo(token.Parent, Me._cancellationToken).Symbol
If symbol IsNot Nothing AndAlso Me._renamedSymbol.Kind <> SymbolKind.Local AndAlso Me._renamedSymbol.Kind <> SymbolKind.RangeVariable AndAlso
(Equals(symbol, Me._renamedSymbol) OrElse SymbolKey.GetComparer(ignoreCase:=True, ignoreAssemblyKeys:=False).Equals(symbol.GetSymbolKey(), Me._renamedSymbol.GetSymbolKey())) Then
Return True
End If
End If
Return False
End If
End Function
Public Overrides Function VisitToken(oldToken As SyntaxToken) As SyntaxToken
If oldToken = Nothing Then
Return oldToken
End If
Dim newToken = oldToken
Dim shouldCheckTrivia = Me._stringAndCommentTextSpans.ContainsKey(oldToken.Span)
If shouldCheckTrivia Then
Me._isProcessingStructuredTrivia += 1
newToken = MyBase.VisitToken(newToken)
Me._isProcessingStructuredTrivia -= 1
Else
newToken = MyBase.VisitToken(newToken)
End If
newToken = UpdateAliasAnnotation(newToken)
' Rename matches in strings and comments
newToken = RenameWithinToken(oldToken, newToken)
' We don't want to annotate XmlName with RenameActionAnnotation
If newToken.Kind = SyntaxKind.XmlNameToken Then
Return newToken
End If
Dim isRenameLocation = IsInRenameLocation(oldToken)
Dim isOldText = CaseInsensitiveComparison.Equals(oldToken.ValueText, _originalText)
Dim tokenNeedsConflictCheck = isRenameLocation OrElse
isOldText OrElse
CaseInsensitiveComparison.Equals(oldToken.ValueText, _replacementText) OrElse
IsPossibleNameConflict(_possibleNameConflicts, oldToken.ValueText)
If tokenNeedsConflictCheck Then
newToken = RenameAndAnnotateAsync(oldToken, newToken, isRenameLocation, isOldText).WaitAndGetResult_CanCallOnBackground(_cancellationToken)
If Not Me._isProcessingComplexifiedSpans Then
_invocationExpressionsNeedingConflictChecks.AddRange(oldToken.GetAncestors(Of InvocationExpressionSyntax)())
End If
End If
Return newToken
End Function
Private Function GetAnnotationForInvocationExpression(invocationExpression As InvocationExpressionSyntax) As RenameActionAnnotation
Dim identifierToken As SyntaxToken = Nothing
Dim expressionOfInvocation = invocationExpression.Expression
While expressionOfInvocation IsNot Nothing
Select Case expressionOfInvocation.Kind
Case SyntaxKind.IdentifierName, SyntaxKind.GenericName
identifierToken = DirectCast(expressionOfInvocation, SimpleNameSyntax).Identifier
Exit While
Case SyntaxKind.SimpleMemberAccessExpression
identifierToken = DirectCast(expressionOfInvocation, MemberAccessExpressionSyntax).Name.Identifier
Exit While
Case SyntaxKind.QualifiedName
identifierToken = DirectCast(expressionOfInvocation, QualifiedNameSyntax).Right.Identifier
Exit While
Case SyntaxKind.ParenthesizedExpression
expressionOfInvocation = DirectCast(expressionOfInvocation, ParenthesizedExpressionSyntax).Expression
Case SyntaxKind.MeExpression
Exit While
Case Else
' This isn't actually an invocation, so there's no member name to check.
Return Nothing
End Select
End While
If identifierToken <> Nothing AndAlso Not Me._annotatedIdentifierTokens.Contains(identifierToken) Then
Dim symbolInfo = Me._semanticModel.GetSymbolInfo(invocationExpression, Me._cancellationToken)
Dim symbols As IEnumerable(Of ISymbol)
If symbolInfo.Symbol Is Nothing Then
Return Nothing
Else
symbols = SpecializedCollections.SingletonEnumerable(symbolInfo.Symbol)
End If
Dim renameDeclarationLocations As RenameDeclarationLocationReference() =
ConflictResolver.CreateDeclarationLocationAnnotationsAsync(_solution, symbols, _cancellationToken).WaitAndGetResult_CanCallOnBackground(_cancellationToken)
Dim renameAnnotation = New RenameActionAnnotation(
identifierToken.Span,
isRenameLocation:=False,
prefix:=Nothing,
suffix:=Nothing,
renameDeclarationLocations:=renameDeclarationLocations,
isOriginalTextLocation:=False,
isNamespaceDeclarationReference:=False,
isInvocationExpression:=True,
isMemberGroupReference:=False)
Return renameAnnotation
End If
Return Nothing
End Function
Public Overrides Function VisitInvocationExpression(node As InvocationExpressionSyntax) As SyntaxNode
Dim result = MyBase.VisitInvocationExpression(node)
If _invocationExpressionsNeedingConflictChecks.Contains(node) Then
Dim renameAnnotation = GetAnnotationForInvocationExpression(node)
If renameAnnotation IsNot Nothing Then
result = Me._renameAnnotations.WithAdditionalAnnotations(result, renameAnnotation)
End If
End If
Return result
End Function
Private Function RenameToken(oldToken As SyntaxToken, newToken As SyntaxToken, prefix As String, suffix As String) As SyntaxToken
Dim parent = oldToken.Parent
Dim currentNewIdentifier = Me._replacementText
Dim oldIdentifier = newToken.ValueText
Dim isAttributeName = SyntaxFacts.IsAttributeName(parent)
If isAttributeName Then
Debug.Assert(Me._renamedSymbol.IsAttribute() OrElse Me._aliasSymbol.Target.IsAttribute())
If oldIdentifier <> Me._renamedSymbol.Name Then
Dim withoutSuffix = String.Empty
If currentNewIdentifier.TryReduceAttributeSuffix(withoutSuffix) Then
currentNewIdentifier = withoutSuffix
End If
End If
Else
If Not String.IsNullOrEmpty(prefix) Then
currentNewIdentifier = prefix + currentNewIdentifier
End If
If Not String.IsNullOrEmpty(suffix) Then
currentNewIdentifier = currentNewIdentifier + suffix
End If
End If
' determine the canonical identifier name (unescaped, no type char, ...)
Dim valueText = currentNewIdentifier
Dim name = SyntaxFactory.ParseName(currentNewIdentifier)
If name.ContainsDiagnostics Then
name = SyntaxFactory.IdentifierName(currentNewIdentifier)
End If
If name.IsKind(SyntaxKind.GlobalName) Then
valueText = currentNewIdentifier
ElseIf name.IsKind(SyntaxKind.IdentifierName) Then
valueText = DirectCast(name, IdentifierNameSyntax).Identifier.ValueText
End If
If Me._isVerbatim Then
newToken = newToken.CopyAnnotationsTo(SyntaxFactory.BracketedIdentifier(newToken.LeadingTrivia, valueText, newToken.TrailingTrivia))
Else
newToken = newToken.CopyAnnotationsTo(SyntaxFactory.Identifier(
newToken.LeadingTrivia,
If(oldToken.GetTypeCharacter() = TypeCharacter.None, currentNewIdentifier, currentNewIdentifier + oldToken.ToString().Last()),
False,
valueText,
oldToken.GetTypeCharacter(),
newToken.TrailingTrivia))
If Me._replacementTextValid AndAlso
oldToken.GetTypeCharacter() <> TypeCharacter.None AndAlso
(SyntaxFacts.GetKeywordKind(valueText) = SyntaxKind.REMKeyword OrElse Me._syntaxFactsService.IsVerbatimIdentifier(newToken)) Then
newToken = Me._renameAnnotations.WithAdditionalAnnotations(newToken, RenameInvalidIdentifierAnnotation.Instance)
End If
End If
If Me._replacementTextValid Then
If newToken.IsBracketed Then
' a reference location should always be tried to be unescaped, whether it was escaped before rename
' or the replacement itself is escaped.
newToken = newToken.WithAdditionalAnnotations(Simplifier.Annotation)
Else
newToken = TryEscapeIdentifierToken(newToken)
End If
End If
Return newToken
End Function
Private Function RenameInStringLiteral(oldToken As SyntaxToken, newToken As SyntaxToken, subSpansToReplace As ImmutableSortedSet(Of TextSpan), createNewStringLiteral As Func(Of SyntaxTriviaList, String, String, SyntaxTriviaList, SyntaxToken)) As SyntaxToken
Dim originalString = newToken.ToString()
Dim replacedString As String = RenameUtilities.ReplaceMatchingSubStrings(originalString, _originalText, _replacementText, subSpansToReplace)
If replacedString <> originalString Then
Dim oldSpan = oldToken.Span
newToken = createNewStringLiteral(newToken.LeadingTrivia, replacedString, replacedString, newToken.TrailingTrivia)
AddModifiedSpan(oldSpan, newToken.Span)
Return newToken.CopyAnnotationsTo(Me._renameAnnotations.WithAdditionalAnnotations(newToken, New RenameTokenSimplificationAnnotation() With {.OriginalTextSpan = oldSpan}))
End If
Return newToken
End Function
Private Function RenameInCommentTrivia(trivia As SyntaxTrivia) As SyntaxTrivia
Dim originalString = trivia.ToString()
Dim replacedString As String = RenameUtilities.ReplaceMatchingSubStrings(originalString, _originalText, _replacementText)
If replacedString <> originalString Then
Dim oldSpan = trivia.Span
Dim newTrivia = SyntaxFactory.CommentTrivia(replacedString)
AddModifiedSpan(oldSpan, newTrivia.Span)
Return trivia.CopyAnnotationsTo(Me._renameAnnotations.WithAdditionalAnnotations(newTrivia, New RenameTokenSimplificationAnnotation() With {.OriginalTextSpan = oldSpan}))
End If
Return trivia
End Function
Private Function RenameInTrivia(token As SyntaxToken, leadingOrTrailingTriviaList As IEnumerable(Of SyntaxTrivia)) As SyntaxToken
Return token.ReplaceTrivia(leadingOrTrailingTriviaList, Function(oldTrivia, newTrivia)
If newTrivia.Kind = SyntaxKind.CommentTrivia Then
Return RenameInCommentTrivia(newTrivia)
End If
Return newTrivia
End Function)
End Function
Private Function RenameWithinToken(oldToken As SyntaxToken, newToken As SyntaxToken) As SyntaxToken
Dim subSpansToReplace As ImmutableSortedSet(Of TextSpan) = Nothing
If Me._isProcessingComplexifiedSpans OrElse
(Me._isProcessingStructuredTrivia = 0 AndAlso Not Me._stringAndCommentTextSpans.TryGetValue(oldToken.Span, subSpansToReplace)) Then
Return newToken
End If
If Me._isRenamingInStrings OrElse subSpansToReplace?.Count > 0 Then
If newToken.Kind = SyntaxKind.StringLiteralToken Then
newToken = RenameInStringLiteral(oldToken, newToken, subSpansToReplace, AddressOf SyntaxFactory.StringLiteralToken)
ElseIf newToken.Kind = SyntaxKind.InterpolatedStringTextToken Then
newToken = RenameInStringLiteral(oldToken, newToken, subSpansToReplace, AddressOf SyntaxFactory.InterpolatedStringTextToken)
End If
End If
If Me._isRenamingInComments Then
If newToken.Kind = SyntaxKind.XmlTextLiteralToken Then
newToken = RenameInStringLiteral(oldToken, newToken, subSpansToReplace, AddressOf SyntaxFactory.XmlTextLiteralToken)
ElseIf newToken.Kind = SyntaxKind.XmlNameToken AndAlso CaseInsensitiveComparison.Equals(oldToken.ValueText, _originalText) Then
Dim newIdentifierToken = SyntaxFactory.XmlNameToken(newToken.LeadingTrivia, _replacementText, SyntaxFacts.GetKeywordKind(_replacementText), newToken.TrailingTrivia)
newToken = newToken.CopyAnnotationsTo(Me._renameAnnotations.WithAdditionalAnnotations(newIdentifierToken, New RenameTokenSimplificationAnnotation() With {.OriginalTextSpan = oldToken.Span}))
AddModifiedSpan(oldToken.Span, newToken.Span)
End If
If newToken.HasLeadingTrivia Then
Dim updatedToken = RenameInTrivia(oldToken, oldToken.LeadingTrivia)
If updatedToken <> oldToken Then
newToken = newToken.WithLeadingTrivia(updatedToken.LeadingTrivia)
End If
End If
If newToken.HasTrailingTrivia Then
Dim updatedToken = RenameInTrivia(oldToken, oldToken.TrailingTrivia)
If updatedToken <> oldToken Then
newToken = newToken.WithTrailingTrivia(updatedToken.TrailingTrivia)
End If
End If
End If
Return newToken
End Function
End Class
#End Region
#Region "Declaration Conflicts"
Public Overrides Function LocalVariableConflict(
token As SyntaxToken,
newReferencedSymbols As IEnumerable(Of ISymbol)) As Boolean
' This scenario is not present in VB and only in C#
Return False
End Function
Public Overrides Function ComputeDeclarationConflictsAsync(
replacementText As String,
renamedSymbol As ISymbol,
renameSymbol As ISymbol,
referencedSymbols As IEnumerable(Of ISymbol),
baseSolution As Solution,
newSolution As Solution,
reverseMappedLocations As IDictionary(Of Location, Location),
cancellationToken As CancellationToken
) As Task(Of ImmutableArray(Of Location))
Dim conflicts = ArrayBuilder(Of Location).GetInstance()
If renamedSymbol.Kind = SymbolKind.Parameter OrElse
renamedSymbol.Kind = SymbolKind.Local OrElse
renamedSymbol.Kind = SymbolKind.RangeVariable Then
Dim token = renamedSymbol.Locations.Single().FindToken(cancellationToken)
' Find the method block or field declaration that we're in. Note the LastOrDefault
' so we find the uppermost one, since VariableDeclarators live in methods too.
Dim methodBase = token.Parent.AncestorsAndSelf.Where(Function(s) TypeOf s Is MethodBlockBaseSyntax OrElse TypeOf s Is VariableDeclaratorSyntax) _
.LastOrDefault()
Dim visitor As New LocalConflictVisitor(token, newSolution, cancellationToken)
visitor.Visit(methodBase)
conflicts.AddRange(visitor.ConflictingTokens.Select(Function(t) t.GetLocation()) _
.Select(Function(loc) reverseMappedLocations(loc)))
' If this is a parameter symbol for a partial method definition, be sure we visited
' the implementation part's body.
If renamedSymbol.Kind = SymbolKind.Parameter AndAlso
renamedSymbol.ContainingSymbol.Kind = SymbolKind.Method Then
Dim methodSymbol = DirectCast(renamedSymbol.ContainingSymbol, IMethodSymbol)
If methodSymbol.PartialImplementationPart IsNot Nothing Then
Dim matchingParameterSymbol = methodSymbol.PartialImplementationPart.Parameters((DirectCast(renamedSymbol, IParameterSymbol)).Ordinal)
token = matchingParameterSymbol.Locations.Single().FindToken(cancellationToken)
methodBase = token.GetAncestor(Of MethodBlockSyntax)
visitor = New LocalConflictVisitor(token, newSolution, cancellationToken)
visitor.Visit(methodBase)
conflicts.AddRange(visitor.ConflictingTokens.Select(Function(t) t.GetLocation()) _
.Select(Function(loc) reverseMappedLocations(loc)))
End If
End If
' in VB parameters of properties are not allowed to be the same as the containing property
If renamedSymbol.Kind = SymbolKind.Parameter AndAlso
renamedSymbol.ContainingSymbol.Kind = SymbolKind.Property AndAlso
CaseInsensitiveComparison.Equals(renamedSymbol.ContainingSymbol.Name, renamedSymbol.Name) Then
Dim propertySymbol = renamedSymbol.ContainingSymbol
While propertySymbol IsNot Nothing
conflicts.AddRange(renamedSymbol.ContainingSymbol.Locations _
.Select(Function(loc) reverseMappedLocations(loc)))
propertySymbol = propertySymbol.GetOverriddenMember()
End While
End If
ElseIf renamedSymbol.Kind = SymbolKind.Label Then
Dim token = renamedSymbol.Locations.Single().FindToken(cancellationToken)
Dim containingMethod = token.Parent.FirstAncestorOrSelf(Of SyntaxNode)(
Function(s) TypeOf s Is MethodBlockBaseSyntax OrElse
TypeOf s Is LambdaExpressionSyntax)
Dim visitor As New LabelConflictVisitor(token)
visitor.Visit(containingMethod)
conflicts.AddRange(visitor.ConflictingTokens.Select(Function(t) t.GetLocation()) _
.Select(Function(loc) reverseMappedLocations(loc)))
ElseIf renamedSymbol.Kind = SymbolKind.Method Then
conflicts.AddRange(
DeclarationConflictHelpers.GetMembersWithConflictingSignatures(DirectCast(renamedSymbol, IMethodSymbol), trimOptionalParameters:=True) _
.Select(Function(loc) reverseMappedLocations(loc)))
ElseIf renamedSymbol.Kind = SymbolKind.Property Then
conflicts.AddRange(
DeclarationConflictHelpers.GetMembersWithConflictingSignatures(DirectCast(renamedSymbol, IPropertySymbol), trimOptionalParameters:=True) _
.Select(Function(loc) reverseMappedLocations(loc)))
AddConflictingParametersOfProperties(
referencedSymbols.Concat(renameSymbol).Where(Function(sym) sym.Kind = SymbolKind.Property),
renamedSymbol.Name,
conflicts)
ElseIf renamedSymbol.Kind = SymbolKind.TypeParameter Then
For Each location In renamedSymbol.Locations
Dim token = location.FindToken(cancellationToken)
Dim currentTypeParameter = token.Parent
For Each typeParameter In DirectCast(currentTypeParameter.Parent, TypeParameterListSyntax).Parameters
If typeParameter IsNot currentTypeParameter AndAlso CaseInsensitiveComparison.Equals(token.ValueText, typeParameter.Identifier.ValueText) Then
conflicts.Add(reverseMappedLocations(typeParameter.Identifier.GetLocation()))
End If
Next
Next
End If
' if the renamed symbol is a type member, it's name should not conflict with a type parameter
If renamedSymbol.ContainingType IsNot Nothing AndAlso renamedSymbol.ContainingType.GetMembers(renamedSymbol.Name).Contains(renamedSymbol) Then
Dim conflictingLocations = renamedSymbol.ContainingType.TypeParameters _
.Where(Function(t) CaseInsensitiveComparison.Equals(t.Name, renamedSymbol.Name)) _
.SelectMany(Function(t) t.Locations)
For Each location In conflictingLocations
Dim typeParameterToken = location.FindToken(cancellationToken)
conflicts.Add(reverseMappedLocations(typeParameterToken.GetLocation()))
Next
End If
Return Task.FromResult(conflicts.ToImmutableAndFree())
End Function
Public Overrides Async Function ComputeImplicitReferenceConflictsAsync(
renameSymbol As ISymbol, renamedSymbol As ISymbol,
implicitReferenceLocations As IEnumerable(Of ReferenceLocation),
cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of Location))
' Handle renaming of symbols used for foreach
Dim implicitReferencesMightConflict = renameSymbol.Kind = SymbolKind.Property AndAlso
CaseInsensitiveComparison.Equals(renameSymbol.Name, "Current")
implicitReferencesMightConflict = implicitReferencesMightConflict OrElse
(renameSymbol.Kind = SymbolKind.Method AndAlso
(CaseInsensitiveComparison.Equals(renameSymbol.Name, "MoveNext") OrElse
CaseInsensitiveComparison.Equals(renameSymbol.Name, "GetEnumerator")))
' TODO: handle Dispose for using statement and Add methods for collection initializers.
If implicitReferencesMightConflict Then
If Not CaseInsensitiveComparison.Equals(renamedSymbol.Name, renameSymbol.Name) Then
For Each implicitReferenceLocation In implicitReferenceLocations
Dim token = Await implicitReferenceLocation.Location.SourceTree.GetTouchingTokenAsync(
implicitReferenceLocation.Location.SourceSpan.Start, cancellationToken, findInsideTrivia:=False).ConfigureAwait(False)
If token.Kind = SyntaxKind.ForKeyword AndAlso token.Parent.IsKind(SyntaxKind.ForEachStatement) Then
Return ImmutableArray.Create(DirectCast(token.Parent, ForEachStatementSyntax).Expression.GetLocation())
End If
Next
End If
End If
Return ImmutableArray(Of Location).Empty
End Function
#End Region
''' <summary>
''' Gets the top most enclosing statement as target to call MakeExplicit on.
''' It's either the enclosing statement, or if this statement is inside of a lambda expression, the enclosing
''' statement of this lambda.
''' </summary>
''' <param name="token">The token to get the complexification target for.</param>
Public Overrides Function GetExpansionTargetForLocation(token As SyntaxToken) As SyntaxNode
Return GetExpansionTarget(token)
End Function
Private Shared Function GetExpansionTarget(token As SyntaxToken) As SyntaxNode
' get the directly enclosing statement
Dim enclosingStatement = token.FirstAncestorOrSelf(Function(n) TypeOf (n) Is ExecutableStatementSyntax)
' for nodes in a using, for or for each statement, we do not need the enclosing _executable_ statement, which is the whole block.
' it's enough to expand the using, for or foreach statement.
Dim possibleSpecialStatement = token.FirstAncestorOrSelf(Function(n) n.Kind = SyntaxKind.ForStatement OrElse
n.Kind = SyntaxKind.ForEachStatement OrElse
n.Kind = SyntaxKind.UsingStatement OrElse
n.Kind = SyntaxKind.CatchBlock)
If possibleSpecialStatement IsNot Nothing Then
If enclosingStatement Is possibleSpecialStatement.Parent Then
enclosingStatement = If(possibleSpecialStatement.Kind = SyntaxKind.CatchBlock,
DirectCast(possibleSpecialStatement, CatchBlockSyntax).CatchStatement,
possibleSpecialStatement)
End If
End If
' see if there's an enclosing lambda expression
Dim possibleLambdaExpression As SyntaxNode = Nothing
If enclosingStatement Is Nothing Then
possibleLambdaExpression = token.FirstAncestorOrSelf(Function(n) TypeOf (n) Is LambdaExpressionSyntax)
End If
Dim enclosingCref = token.FirstAncestorOrSelf(Function(n) TypeOf (n) Is CrefReferenceSyntax)
If enclosingCref IsNot Nothing Then
Return enclosingCref
End If
' there seems to be no statement above this one. Let's see if we can at least get an SimpleNameSyntax
Return If(enclosingStatement, If(possibleLambdaExpression, token.FirstAncestorOrSelf(Function(n) TypeOf (n) Is SimpleNameSyntax)))
End Function
#Region "Helper Methods"
Public Overrides Function IsIdentifierValid(replacementText As String, syntaxFactsService As ISyntaxFactsService) As Boolean
replacementText = SyntaxFacts.MakeHalfWidthIdentifier(replacementText)
Dim possibleIdentifier As String
If syntaxFactsService.IsTypeCharacter(replacementText.Last()) Then
' We don't allow to use identifiers with type characters
Return False
Else
If replacementText.StartsWith("[", StringComparison.Ordinal) AndAlso replacementText.EndsWith("]", StringComparison.Ordinal) Then
possibleIdentifier = replacementText
Else
possibleIdentifier = "[" & replacementText & "]"
End If
End If
' Make sure we got an identifier.
If Not syntaxFactsService.IsValidIdentifier(possibleIdentifier) Then
' We still don't have an identifier, so let's fail
Return False
End If
' This is a valid Identifier
Return True
End Function
Public Overrides Function ComputePossibleImplicitUsageConflicts(
renamedSymbol As ISymbol,
semanticModel As SemanticModel,
originalDeclarationLocation As Location,
newDeclarationLocationStartingPosition As Integer,
cancellationToken As CancellationToken) As ImmutableArray(Of Location)
' TODO: support other implicitly used methods like dispose
If CaseInsensitiveComparison.Equals(renamedSymbol.Name, "MoveNext") OrElse
CaseInsensitiveComparison.Equals(renamedSymbol.Name, "GetEnumerator") OrElse
CaseInsensitiveComparison.Equals(renamedSymbol.Name, "Current") Then
If TypeOf renamedSymbol Is IMethodSymbol Then
If DirectCast(renamedSymbol, IMethodSymbol).IsOverloads AndAlso
(renamedSymbol.GetAllTypeArguments().Length <> 0 OrElse
DirectCast(renamedSymbol, IMethodSymbol).Parameters.Length <> 0) Then
Return ImmutableArray(Of Location).Empty
End If
End If
If TypeOf renamedSymbol Is IPropertySymbol Then
If DirectCast(renamedSymbol, IPropertySymbol).IsOverloads Then
Return ImmutableArray(Of Location).Empty
End If
End If
' TODO: Partial methods currently only show the location where the rename happens As a conflict.
' Consider showing both locations as a conflict.
Dim baseType = renamedSymbol.ContainingType?.GetBaseTypes().FirstOrDefault()
If baseType IsNot Nothing Then
Dim implicitSymbols = semanticModel.LookupSymbols(
newDeclarationLocationStartingPosition,
baseType,
renamedSymbol.Name) _
.Where(Function(sym) Not sym.Equals(renamedSymbol))
For Each symbol In implicitSymbols
If symbol.GetAllTypeArguments().Length <> 0 Then
Continue For
End If
If symbol.Kind = SymbolKind.Method Then
Dim method = DirectCast(symbol, IMethodSymbol)
If CaseInsensitiveComparison.Equals(symbol.Name, "MoveNext") Then
If Not method.ReturnsVoid AndAlso Not method.Parameters.Any() AndAlso method.ReturnType.SpecialType = SpecialType.System_Boolean Then
Return ImmutableArray.Create(originalDeclarationLocation)
End If
ElseIf CaseInsensitiveComparison.Equals(symbol.Name, "GetEnumerator") Then
' we are a bit pessimistic here.
' To be sure we would need to check if the returned type Is having a MoveNext And Current as required by foreach
If Not method.ReturnsVoid AndAlso
Not method.Parameters.Any() Then
Return ImmutableArray.Create(originalDeclarationLocation)
End If
End If
ElseIf CaseInsensitiveComparison.Equals(symbol.Name, "Current") Then
Dim [property] = DirectCast(symbol, IPropertySymbol)
If Not [property].Parameters.Any() AndAlso Not [property].IsWriteOnly Then
Return ImmutableArray.Create(originalDeclarationLocation)
End If
End If
Next
End If
End If
Return ImmutableArray(Of Location).Empty
End Function
Public Overrides Sub TryAddPossibleNameConflicts(symbol As ISymbol, replacementText As String, possibleNameConflicts As ICollection(Of String))
Dim halfWidthReplacementText = SyntaxFacts.MakeHalfWidthIdentifier(replacementText)
Const AttributeSuffix As String = "Attribute"
Const AttributeSuffixLength As Integer = 9
Debug.Assert(AttributeSuffixLength = AttributeSuffix.Length, "Assert (AttributeSuffixLength = AttributeSuffix.Length) failed.")
If replacementText.Length > AttributeSuffixLength AndAlso CaseInsensitiveComparison.Equals(halfWidthReplacementText.Substring(halfWidthReplacementText.Length - AttributeSuffixLength), AttributeSuffix) Then
Dim conflict = replacementText.Substring(0, replacementText.Length - AttributeSuffixLength)
If Not possibleNameConflicts.Contains(conflict) Then
possibleNameConflicts.Add(conflict)
End If
End If
If symbol.Kind = SymbolKind.Property Then
For Each conflict In {"_" + replacementText, "get_" + replacementText, "set_" + replacementText}
If Not possibleNameConflicts.Contains(conflict) Then
possibleNameConflicts.Add(conflict)
End If
Next
End If
' consider both versions of the identifier (escaped and unescaped)
Dim valueText = replacementText
Dim kind = SyntaxFacts.GetKeywordKind(replacementText)
If kind <> SyntaxKind.None Then
valueText = SyntaxFacts.GetText(kind)
Else
Dim name = SyntaxFactory.ParseName(replacementText)
If name.Kind = SyntaxKind.IdentifierName Then
valueText = DirectCast(name, IdentifierNameSyntax).Identifier.ValueText
End If
End If
If Not CaseInsensitiveComparison.Equals(valueText, replacementText) Then
possibleNameConflicts.Add(valueText)
End If
End Sub
''' <summary>
''' Gets the semantic model for the given node.
''' If the node belongs to the syntax tree of the original semantic model, then returns originalSemanticModel.
''' Otherwise, returns a speculative model.
''' The assumption for the later case is that span start position of the given node in it's syntax tree is same as
''' the span start of the original node in the original syntax tree.
''' </summary>
''' <param name="node"></param>
''' <param name="originalSemanticModel"></param>
Public Shared Function GetSemanticModelForNode(node As SyntaxNode, originalSemanticModel As SemanticModel) As SemanticModel
If node.SyntaxTree Is originalSemanticModel.SyntaxTree Then
' This is possible if the previous rename phase didn't rewrite any nodes in this tree.
Return originalSemanticModel
End If
Dim syntax = node
Dim nodeToSpeculate = syntax.GetAncestorsOrThis(Of SyntaxNode).Where(Function(n) SpeculationAnalyzer.CanSpeculateOnNode(n)).LastOrDefault
If nodeToSpeculate Is Nothing Then
If syntax.IsKind(SyntaxKind.CrefReference) Then
nodeToSpeculate = DirectCast(syntax, CrefReferenceSyntax).Name
ElseIf syntax.IsKind(SyntaxKind.TypeConstraint) Then
nodeToSpeculate = DirectCast(syntax, TypeConstraintSyntax).Type
Else
Return Nothing
End If
End If
Dim isInNamespaceOrTypeContext = SyntaxFacts.IsInNamespaceOrTypeContext(TryCast(syntax, ExpressionSyntax))
Dim position = nodeToSpeculate.SpanStart
Return SpeculationAnalyzer.CreateSpeculativeSemanticModelForNode(nodeToSpeculate, originalSemanticModel, position, isInNamespaceOrTypeContext)
End Function
#End Region
End Class
End Namespace
|