|
' 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 Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.SymbolDisplay
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
'Imports SemanticModel = Microsoft.CodeAnalysis.VisualBasic.Semantics.SemanticModel
Namespace Microsoft.CodeAnalysis.VisualBasic
Partial Friend Class SymbolDisplayVisitor
Inherits AbstractSymbolDisplayVisitor
Private Shared ReadOnly s_visitorPool As New ObjectPool(Of SymbolDisplayVisitor)(Function(pool) New SymbolDisplayVisitor(pool), 128)
Private ReadOnly _pool As ObjectPool(Of SymbolDisplayVisitor)
Private _escapeKeywordIdentifiers As Boolean
' A Symbol in VB might be a PENamedSymbolWithEmittedNamespaceName and in that case the
' casing of the contained types and namespaces might differ because of merged classes/namespaces.
' To maintain the original spelling an emittedNamespaceName with the correct spelling is passed to
' this visitor.
Private Sub New(pool As ObjectPool(Of SymbolDisplayVisitor))
_pool = pool
End Sub
Friend Shared Function GetInstance(
builder As ArrayBuilder(Of SymbolDisplayPart),
format As SymbolDisplayFormat,
semanticModelOpt As SemanticModel,
positionOpt As Integer) As SymbolDisplayVisitor
Dim instance = s_visitorPool.Allocate()
instance.Initialize(builder, format, isFirstSymbolVisited:=True, semanticModelOpt, positionOpt, inNamespaceOrType:=False)
Return instance
End Function
Private Shared Function GetInstance(
builder As ArrayBuilder(Of SymbolDisplayPart),
format As SymbolDisplayFormat,
semanticModelOpt As SemanticModel,
positionOpt As Integer,
escapeKeywordIdentifiers As Boolean,
isFirstSymbolVisited As Boolean,
inNamespaceOrType As Boolean) As SymbolDisplayVisitor
Dim instance = s_visitorPool.Allocate()
instance.Initialize(builder, format, isFirstSymbolVisited, semanticModelOpt, positionOpt, inNamespaceOrType)
instance._escapeKeywordIdentifiers = escapeKeywordIdentifiers
Return instance
End Function
Protected Shadows Sub Initialize(builder As ArrayBuilder(Of SymbolDisplayPart), format As SymbolDisplayFormat, isFirstSymbolVisited As Boolean, semanticModelOpt As SemanticModel, positionOpt As Integer, inNamespaceOrType As Boolean)
Debug.Assert(format IsNot Nothing, "Format must not be null")
MyBase.Initialize(builder, format, isFirstSymbolVisited, semanticModelOpt, positionOpt, inNamespaceOrType)
Me._escapeKeywordIdentifiers = format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers)
End Sub
Public Overrides Sub Free()
MyBase.Free()
_escapeKeywordIdentifiers = False
_pool.Free(Me)
End Sub
' in case the display of a symbol is different for a type that acts as a container, use this visitor
Protected Overrides Function MakeNotFirstVisitor(Optional inNamespaceOrType As Boolean = False) As AbstractSymbolDisplayVisitor
Return GetInstance(
Me.Builder,
Me.Format,
Me.SemanticModelOpt,
Me.PositionOpt,
Me._escapeKeywordIdentifiers,
isFirstSymbolVisited:=False,
inNamespaceOrType:=inNamespaceOrType)
End Function
Protected Overrides Sub FreeNotFirstVisitor(visitor As AbstractSymbolDisplayVisitor)
Debug.Assert(visitor IsNot Me)
visitor.Free()
End Sub
Friend Function CreatePart(kind As SymbolDisplayPartKind,
symbol As ISymbol,
text As String,
noEscaping As Boolean) As SymbolDisplayPart
Dim escape = (AlwaysEscape(kind, text) OrElse Not noEscaping) AndAlso _escapeKeywordIdentifiers AndAlso IsEscapable(kind)
Return New SymbolDisplayPart(kind, symbol, If(escape, EscapeIdentifier(text), text))
End Function
Private Shared Function AlwaysEscape(kind As SymbolDisplayPartKind, text As String) As Boolean
If kind <> SymbolDisplayPartKind.Keyword Then
' We must always escape "Rem" and "New" when they are being used in an identifier context.
' For constructors, (say C.New) we emit New as a Keyword kind if it's a constructor, or
' as an appropriate name kind if it's an identifier.
If CaseInsensitiveComparison.Equals(SyntaxFacts.GetText(SyntaxKind.REMKeyword), text) OrElse
CaseInsensitiveComparison.Equals(SyntaxFacts.GetText(SyntaxKind.NewKeyword), text) Then
Return True
End If
End If
Return False
End Function
Private Shared Function IsEscapable(kind As SymbolDisplayPartKind) As Boolean
Select Case kind
Case SymbolDisplayPartKind.ModuleName,
SymbolDisplayPartKind.ClassName,
SymbolDisplayPartKind.StructName,
SymbolDisplayPartKind.InterfaceName,
SymbolDisplayPartKind.EnumName,
SymbolDisplayPartKind.DelegateName,
SymbolDisplayPartKind.TypeParameterName,
SymbolDisplayPartKind.MethodName,
SymbolDisplayPartKind.PropertyName,
SymbolDisplayPartKind.FieldName,
SymbolDisplayPartKind.LocalName,
SymbolDisplayPartKind.NamespaceName,
SymbolDisplayPartKind.ParameterName,
SymbolDisplayPartKind.AliasName,
SymbolDisplayPartKind.ErrorTypeName,
SymbolDisplayPartKind.LabelName,
SymbolDisplayPartKind.EventName,
SymbolDisplayPartKind.RangeVariableName
Return True
Case Else
Return False
End Select
End Function
Private Function EscapeIdentifier(identifier As String) As String
' always escape keywords,
If SyntaxFacts.GetKeywordKind(identifier) <> SyntaxKind.None Then
Return String.Format("[{0}]", identifier)
End If
' The minimal flag is e.g. used by the service layer when generating code (example: simplify name).
' Unfortunately we do not know in what context the identifier is used and we need to assume the worst case here
' to avoid ambiguities while parsing the resulting code.
If Me.IsMinimizing Then
Dim contextualKeywordKind As SyntaxKind = SyntaxFacts.GetContextualKeywordKind(identifier)
' Leading implicit line continuation is allowed before query operators (Aggregate, Distinct, From, Group By,
' Group Join, Join, Let, Order By, Select, Skip, Skip While, Take, Take While, Where, In, Into, On, Ascending,
' and Descending).
' If the current identifier is one of these keywords, we need to escape them to avoid parsing issues if the
' previous line was a query expression.
'
' In addition to the query operators, we need to escape the identifier "preserve" to avoid ambiguities
' inside of a dim statement (dim [preserve].ArrayName(1))
Select Case contextualKeywordKind
Case SyntaxKind.AggregateKeyword,
SyntaxKind.DistinctKeyword,
SyntaxKind.FromKeyword,
SyntaxKind.GroupKeyword,
SyntaxKind.JoinKeyword,
SyntaxKind.LetKeyword,
SyntaxKind.OrderKeyword,
SyntaxKind.SelectKeyword,
SyntaxKind.SkipKeyword,
SyntaxKind.TakeKeyword,
SyntaxKind.WhereKeyword,
SyntaxKind.InKeyword,
SyntaxKind.IntoKeyword,
SyntaxKind.OnKeyword,
SyntaxKind.AscendingKeyword,
SyntaxKind.DescendingKeyword,
SyntaxKind.PreserveKeyword
Return String.Format("[{0}]", identifier)
End Select
End If
Return identifier
End Function
Public Overrides Sub VisitAssembly(symbol As IAssemblySymbol)
Dim text = If((Format.TypeQualificationStyle = SymbolDisplayTypeQualificationStyle.NameOnly), symbol.Identity.Name, symbol.Identity.GetDisplayName())
Builder.Add(CreatePart(SymbolDisplayPartKind.AssemblyName, symbol, text, False))
End Sub
Public Overrides Sub VisitLabel(symbol As ILabelSymbol)
Builder.Add(CreatePart(SymbolDisplayPartKind.LabelName, symbol, symbol.Name, False))
End Sub
Public Overrides Sub VisitAlias(symbol As IAliasSymbol)
Builder.Add(CreatePart(SymbolDisplayPartKind.LocalName, symbol, symbol.Name, False))
If Format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeType) Then
AddPunctuation(SyntaxKind.EqualsToken)
symbol.Target.Accept(Me)
End If
End Sub
Public Overrides Sub VisitModule(symbol As IModuleSymbol)
Builder.Add(CreatePart(SymbolDisplayPartKind.ModuleName, symbol, symbol.Name, False))
End Sub
Public Overloads Overrides Sub VisitNamespace(symbol As INamespaceSymbol)
If IsFirstSymbolVisited AndAlso Format.KindOptions.IncludesOption(SymbolDisplayKindOptions.IncludeNamespaceKeyword) Then
AddKeyword(SyntaxKind.NamespaceKeyword)
AddSpace()
End If
VisitNamespace(symbol, String.Empty)
End Sub
Private Overloads Sub VisitNamespace(symbol As INamespaceSymbol, emittedName As String)
Dim myCaseCorrectedNSName As String = symbol.Name
Dim myCaseCorrectedParentNSName As String = String.Empty
If Not emittedName.IsEmpty Then
Dim nsIdx = emittedName.LastIndexOf("."c)
If nsIdx > -1 Then
myCaseCorrectedNSName = emittedName.Substring(nsIdx + 1)
myCaseCorrectedParentNSName = emittedName.Substring(0, nsIdx)
Else
myCaseCorrectedNSName = emittedName
End If
End If
If Me.IsMinimizing Then
If TryAddAlias(symbol, Builder) Then
Return
End If
MinimallyQualify(symbol, myCaseCorrectedNSName, myCaseCorrectedParentNSName)
Return
End If
Dim visitedParents As Boolean = False
If Format.TypeQualificationStyle = SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces Then
Dim containingNamespace = symbol.ContainingNamespace
' add "Namespace " if SymbolDisplayKindOptions.IncludeKind is set
' this is not handled in AddTypeKind in AddTypeKind()
If containingNamespace Is Nothing AndAlso Format.KindOptions.IncludesOption(SymbolDisplayKindOptions.IncludeNamespaceKeyword) Then
AddKeyword(SyntaxKind.NamespaceKeyword)
AddSpace()
End If
If ShouldVisitNamespace(containingNamespace) Then
VisitNamespace(containingNamespace, myCaseCorrectedParentNSName)
AddOperator(SyntaxKind.DotToken)
visitedParents = True
End If
End If
If symbol.IsGlobalNamespace Then
AddGlobalNamespace(symbol)
Else
Builder.Add(CreatePart(SymbolDisplayPartKind.NamespaceName, symbol, myCaseCorrectedNSName, visitedParents))
End If
End Sub
Private Sub AddGlobalNamespace(symbol As INamespaceSymbol)
Select Case Format.GlobalNamespaceStyle
Case SymbolDisplayGlobalNamespaceStyle.Omitted
Case SymbolDisplayGlobalNamespaceStyle.Included
Builder.Add(CreatePart(SymbolDisplayPartKind.Keyword, symbol, SyntaxFacts.GetText(SyntaxKind.GlobalKeyword), True))
Case SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining
Debug.Assert(Me.IsFirstSymbolVisited, "Don't call with IsFirstSymbolVisited = false if OmittedAsContaining")
Builder.Add(CreatePart(SymbolDisplayPartKind.Keyword, symbol, SyntaxFacts.GetText(SyntaxKind.GlobalKeyword), True))
Case Else
Throw ExceptionUtilities.UnexpectedValue(Format.GlobalNamespaceStyle)
End Select
End Sub
Public Overrides Sub VisitLocal(symbol As ILocalSymbol)
' Locals can be synthesized by the compiler in many cases (for example the implicit
' local in a function that has the same name as the containing function). In some
' cases the locals are anonymous (for example, a similar local in an operator).
'
' These anonymous locals should not be exposed through public APIs. However, for
' testing purposes we occasionally may print them out. In this case, give them
' a reasonable name so that tests can clearly describe what these are.
Dim name = If(symbol.Name, "<anonymous local>")
If symbol.IsConst Then
Builder.Add(CreatePart(SymbolDisplayPartKind.ConstantName, symbol, name, noEscaping:=False))
Else
Builder.Add(CreatePart(SymbolDisplayPartKind.LocalName, symbol, name, noEscaping:=False))
End If
If Format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeType) Then
AddSpace()
AddKeyword(SyntaxKind.AsKeyword)
AddSpace()
symbol.Type.Accept(Me)
End If
If symbol.IsConst AndAlso symbol.HasConstantValue AndAlso Format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeConstantValue) Then
AddSpace()
AddPunctuation(SyntaxKind.EqualsToken)
AddSpace()
AddConstantValue(symbol.Type, symbol.ConstantValue)
End If
End Sub
Public Overrides Sub VisitRangeVariable(symbol As IRangeVariableSymbol)
Builder.Add(CreatePart(SymbolDisplayPartKind.RangeVariableName, symbol, symbol.Name, False))
If Format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeType) Then
Dim vbRangeVariable = TryCast(symbol, RangeVariableSymbol)
If vbRangeVariable IsNot Nothing Then
AddSpace()
AddKeyword(SyntaxKind.AsKeyword)
AddSpace()
DirectCast(vbRangeVariable.Type, ITypeSymbol).Accept(Me)
End If
End If
End Sub
Friend Overrides Sub VisitPreprocessing(symbol As IPreprocessingSymbol)
Dim part = New SymbolDisplayPart(SymbolDisplayPartKind.Text, symbol, symbol.Name)
builder.Add(part)
End Sub
Protected Overrides Sub AddSpace()
Builder.Add(CreatePart(SymbolDisplayPartKind.Space, Nothing, " ", False))
End Sub
Private Sub AddOperator(operatorKind As SyntaxKind)
Builder.Add(CreatePart(SymbolDisplayPartKind.Operator, Nothing, SyntaxFacts.GetText(operatorKind), False))
End Sub
Private Sub AddPunctuation(punctuationKind As SyntaxKind)
Builder.Add(CreatePart(SymbolDisplayPartKind.Punctuation, Nothing, SyntaxFacts.GetText(punctuationKind), False))
End Sub
Private Sub AddPseudoPunctuation(text As String)
Builder.Add(CreatePart(SymbolDisplayPartKind.Punctuation, Nothing, text, False))
End Sub
Private Sub AddKeyword(keywordKind As SyntaxKind)
Builder.Add(CreatePart(SymbolDisplayPartKind.Keyword, Nothing, SyntaxFacts.GetText(keywordKind), False))
End Sub
Private Sub AddAccessibilityIfRequired(symbol As ISymbol)
AssertContainingSymbol(symbol)
Dim containingType = symbol.ContainingType
If Format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeAccessibility) AndAlso
(containingType Is Nothing OrElse
(containingType.TypeKind <> TypeKind.Interface AndAlso Not IsEnumMember(symbol))) Then
Select Case symbol.DeclaredAccessibility
Case Accessibility.Private
AddKeyword(SyntaxKind.PrivateKeyword)
Case Accessibility.Internal
AddKeyword(SyntaxKind.FriendKeyword)
Case Accessibility.Protected
AddKeyword(SyntaxKind.ProtectedKeyword)
Case Accessibility.ProtectedAndInternal
AddKeyword(SyntaxKind.PrivateKeyword)
AddSpace()
AddKeyword(SyntaxKind.ProtectedKeyword)
Case Accessibility.ProtectedOrInternal
AddKeyword(SyntaxKind.ProtectedKeyword)
AddSpace()
AddKeyword(SyntaxKind.FriendKeyword)
Case Accessibility.Public
AddKeyword(SyntaxKind.PublicKeyword)
Case Else
Throw ExceptionUtilities.UnexpectedValue(symbol.DeclaredAccessibility)
End Select
AddSpace()
End If
End Sub
Private Function ShouldVisitNamespace(containingSymbol As ISymbol) As Boolean
Return _
containingSymbol IsNot Nothing AndAlso
containingSymbol.Kind = SymbolKind.Namespace AndAlso
Format.TypeQualificationStyle = SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces AndAlso
(Not (DirectCast(containingSymbol, INamespaceSymbol)).IsGlobalNamespace OrElse
Format.GlobalNamespaceStyle = SymbolDisplayGlobalNamespaceStyle.Included)
End Function
Private Function IncludeNamedType(namedType As INamedTypeSymbol) As Boolean
Return _
namedType IsNot Nothing AndAlso
(Not namedType.IsScriptClass OrElse Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.IncludeScriptType))
End Function
Private Shared Function IsEnumMember(symbol As ISymbol) As Boolean
Return _
symbol IsNot Nothing AndAlso
symbol.Kind = SymbolKind.Field AndAlso
symbol.ContainingType IsNot Nothing AndAlso
symbol.ContainingType.TypeKind = TypeKind.Enum AndAlso
symbol.Name <> WellKnownMemberNames.EnumBackingFieldName
End Function
End Class
End Namespace
|