|
' 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.EncapsulateField
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.EncapsulateField
<ExportLanguageService(GetType(IEncapsulateFieldService), LanguageNames.VisualBasic), [Shared]>
Friend NotInheritable Class VisualBasicEncapsulateFieldService
Inherits AbstractEncapsulateFieldService(Of ConstructorBlockSyntax)
<ImportingConstructor>
<Obsolete(MefConstruction.ImportingConstructorMessage, True)>
Public Sub New()
End Sub
Protected Overrides Async Function RewriteFieldNameAndAccessibilityAsync(
originalFieldName As String,
makePrivate As Boolean,
document As Document,
declarationAnnotation As SyntaxAnnotation,
cancellationToken As CancellationToken) As Task(Of SyntaxNode)
Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False)
Dim fieldIdentifier = root.GetAnnotatedNodes(Of ModifiedIdentifierSyntax)(declarationAnnotation).FirstOrDefault()
' There may be no field to rewrite if this document is part of a set of linked files
' and the declaration is not conditionally compiled in this document's project.
If fieldIdentifier Is Nothing Then
Return root
End If
Dim identifier = fieldIdentifier.Identifier
Dim annotation = New SyntaxAnnotation()
Dim escapedName = originalFieldName.EscapeIdentifier()
Dim newIdentifier = SyntaxFactory.Identifier(
text:=escapedName,
isBracketed:=escapedName <> originalFieldName,
identifierText:=originalFieldName,
typeCharacter:=TypeCharacter.None)
root = root.ReplaceNode(fieldIdentifier, fieldIdentifier.WithIdentifier(newIdentifier).WithAdditionalAnnotations(annotation, Formatter.Annotation))
fieldIdentifier = root.GetAnnotatedNodes(Of ModifiedIdentifierSyntax)(annotation).First()
If (DirectCast(fieldIdentifier.Parent, VariableDeclaratorSyntax).Names.Count = 1) Then
Dim fieldDeclaration = DirectCast(fieldIdentifier.Parent.Parent, FieldDeclarationSyntax)
Dim modifierKinds = {SyntaxKind.FriendKeyword, SyntaxKind.ProtectedKeyword, SyntaxKind.ProtectedKeyword, SyntaxKind.PrintStatement, SyntaxKind.PublicKeyword}
If makePrivate Then
Dim useableModifiers = fieldDeclaration.Modifiers.Where(Function(m) Not modifierKinds.Contains(m.Kind))
Dim newModifiers = SpecializedCollections.SingletonEnumerable(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)).Concat(useableModifiers)
Dim updatedDeclaration = fieldDeclaration.WithModifiers(SyntaxFactory.TokenList(newModifiers)) _
.WithLeadingTrivia(fieldDeclaration.GetLeadingTrivia()) _
.WithTrailingTrivia(fieldDeclaration.GetTrailingTrivia()) _
.WithAdditionalAnnotations(Formatter.Annotation)
Return root.ReplaceNode(fieldDeclaration, updatedDeclaration)
End If
End If
Return root
End Function
Protected Overrides Async Function GetFieldsAsync(document As Document, span As TextSpan, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of IFieldSymbol))
Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False)
Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False)
Dim fields = root.DescendantNodes(Function(n) n.Span.IntersectsWith(span)) _
.OfType(Of FieldDeclarationSyntax)() _
.Where(Function(n) n.Span.IntersectsWith(span) AndAlso CanEncapsulate(n))
Dim names As IEnumerable(Of ModifiedIdentifierSyntax)
If span.IsEmpty Then
' no selection, get all variables
names = fields.SelectMany(Function(f) f.Declarators.SelectMany(Function(d) d.Names))
Else
' has selection, get only the ones that are included in the selection
names = fields.SelectMany(Function(f) f.Declarators.SelectMany(Function(d) d.Names.Where(Function(n) n.Span.IntersectsWith(span))))
End If
Return names.Select(Function(n) semanticModel.GetDeclaredSymbol(n)).
OfType(Of IFieldSymbol)().
WhereNotNull().
Where(Function(f) f.Name.Length > 0).
ToImmutableArray()
End Function
Private Shared Function CanEncapsulate(field As FieldDeclarationSyntax) As Boolean
Return TypeOf field.Parent Is TypeBlockSyntax
End Function
Protected Shared Function MakeUnique(baseName As String, originalFieldName As String, containingType As INamedTypeSymbol, Optional willChangeFieldName As Boolean = True) As String
If willChangeFieldName Then
Return NameGenerator.GenerateUniqueName(baseName, containingType.MemberNames.Where(Function(x) x <> originalFieldName).ToSet(), StringComparer.OrdinalIgnoreCase)
Else
Return NameGenerator.GenerateUniqueName(baseName, containingType.MemberNames.ToSet(), StringComparer.OrdinalIgnoreCase)
End If
End Function
Protected Overrides Function GenerateFieldAndPropertyNames(field As IFieldSymbol) As (fieldName As String, propertyName As String)
' If the field is marked shadows, it will keep its name.
If field.DeclaredAccessibility = Accessibility.Private OrElse IsShadows(field) Then
Dim propertyName = GeneratePropertyName(field.Name)
propertyName = MakeUnique(propertyName, field)
Return (field.Name, propertyName)
Else
Dim propertyName = GeneratePropertyName(field.Name)
Dim containingTypeMemberNames = field.ContainingType.GetAccessibleMembersInThisAndBaseTypes(Of ISymbol)(field.ContainingType).Select(Function(s) s.Name)
propertyName = NameGenerator.GenerateUniqueName(propertyName, containingTypeMemberNames.Where(Function(m) m <> field.Name).ToSet(), StringComparer.OrdinalIgnoreCase)
Dim newFieldName = MakeUnique("_" + Char.ToLower(propertyName(0)) + propertyName.Substring(1), field)
Return (newFieldName, propertyName)
End If
End Function
Private Shared Function IsShadows(field As IFieldSymbol) As Boolean
Return field.DeclaringSyntaxReferences.Any(Function(d) d.GetSyntax().GetAncestor(Of FieldDeclarationSyntax)().Modifiers.Any(SyntaxKind.ShadowsKeyword))
End Function
Private Shared Function MakeUnique(propertyName As String, field As IFieldSymbol) As String
Dim containingTypeMemberNames = field.ContainingType.GetAccessibleMembersInThisAndBaseTypes(Of ISymbol)(field.ContainingType).Select(Function(s) s.Name)
Return NameGenerator.GenerateUniqueName(propertyName, containingTypeMemberNames.ToSet(), StringComparer.OrdinalIgnoreCase)
End Function
Protected Overrides Function GetConstructorNodes(containingType As INamedTypeSymbol) As IEnumerable(Of ConstructorBlockSyntax)
Return containingType.Constructors.
SelectMany(Function(c As IMethodSymbol) c.DeclaringSyntaxReferences.Select(Function(d) d.GetSyntax().Parent)).
OfType(Of ConstructorBlockSyntax)()
End Function
End Class
End Namespace
|