File: SymbolDisplay\SymbolDisplayVisitor.cs
Web Access
Project: ..\..\..\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.SymbolDisplay;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal partial class SymbolDisplayVisitor : AbstractSymbolDisplayVisitor
    {
        private readonly bool _escapeKeywordIdentifiers;
        private IDictionary<INamespaceOrTypeSymbol, IAliasSymbol> _lazyAliasMap;
 
        internal SymbolDisplayVisitor(
            ArrayBuilder<SymbolDisplayPart> builder,
            SymbolDisplayFormat format,
            SemanticModel semanticModelOpt,
            int positionOpt)
            : base(builder, format, true, semanticModelOpt, positionOpt)
        {
            _escapeKeywordIdentifiers = format.MiscellaneousOptions.IncludesOption(SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers);
        }
 
        private SymbolDisplayVisitor(
            ArrayBuilder<SymbolDisplayPart> builder,
            SymbolDisplayFormat format,
            SemanticModel semanticModelOpt,
            int positionOpt,
            bool escapeKeywordIdentifiers,
            IDictionary<INamespaceOrTypeSymbol, IAliasSymbol> aliasMap,
            bool isFirstSymbolVisited,
            bool inNamespaceOrType = false)
            : base(builder, format, isFirstSymbolVisited, semanticModelOpt, positionOpt, inNamespaceOrType)
        {
            _escapeKeywordIdentifiers = escapeKeywordIdentifiers;
            _lazyAliasMap = aliasMap;
        }
 
        protected override AbstractSymbolDisplayVisitor MakeNotFirstVisitor(bool inNamespaceOrType = false)
        {
            return new SymbolDisplayVisitor(
                this.builder,
                this.format,
                this.semanticModelOpt,
                this.positionOpt,
                _escapeKeywordIdentifiers,
                _lazyAliasMap,
                isFirstSymbolVisited: false,
                inNamespaceOrType: inNamespaceOrType);
        }
 
        internal SymbolDisplayPart CreatePart(SymbolDisplayPartKind kind, ISymbol symbol, string text)
        {
            text = (text == null) ? "?" :
                   (_escapeKeywordIdentifiers && IsEscapable(kind)) ? EscapeIdentifier(text) : text;
 
            return new SymbolDisplayPart(kind, symbol, text);
        }
 
        private static bool IsEscapable(SymbolDisplayPartKind kind)
        {
            switch (kind)
            {
                case SymbolDisplayPartKind.AliasName:
                case SymbolDisplayPartKind.ClassName:
                case SymbolDisplayPartKind.RecordClassName:
                case SymbolDisplayPartKind.StructName:
                case SymbolDisplayPartKind.InterfaceName:
                case SymbolDisplayPartKind.EnumName:
                case SymbolDisplayPartKind.DelegateName:
                case SymbolDisplayPartKind.TypeParameterName:
                case SymbolDisplayPartKind.MethodName:
                case SymbolDisplayPartKind.PropertyName:
                case SymbolDisplayPartKind.FieldName:
                case SymbolDisplayPartKind.LocalName:
                case SymbolDisplayPartKind.NamespaceName:
                case SymbolDisplayPartKind.ParameterName:
                    return true;
                default:
                    return false;
            }
        }
 
        private static string EscapeIdentifier(string identifier)
        {
            var kind = SyntaxFacts.GetKeywordKind(identifier);
            return kind == SyntaxKind.None
                ? identifier
                : $"@{identifier}";
        }
 
        public override void VisitAssembly(IAssemblySymbol symbol)
        {
            var text = format.TypeQualificationStyle == SymbolDisplayTypeQualificationStyle.NameOnly
                ? symbol.Identity.Name
                : symbol.Identity.GetDisplayName();
 
            builder.Add(CreatePart(SymbolDisplayPartKind.AssemblyName, symbol, text));
        }
 
        public override void VisitModule(IModuleSymbol symbol)
        {
            builder.Add(CreatePart(SymbolDisplayPartKind.ModuleName, symbol, symbol.Name));
        }
 
        public override void VisitNamespace(INamespaceSymbol symbol)
        {
            if (this.IsMinimizing)
            {
                if (TryAddAlias(symbol, builder))
                {
                    return;
                }
 
                MinimallyQualify(symbol);
                return;
            }
 
            if (isFirstSymbolVisited && format.KindOptions.IncludesOption(SymbolDisplayKindOptions.IncludeNamespaceKeyword))
            {
                AddKeyword(SyntaxKind.NamespaceKeyword);
                AddSpace();
            }
 
            if (format.TypeQualificationStyle == SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces)
            {
                var containingNamespace = symbol.ContainingNamespace;
                if (ShouldVisitNamespace(containingNamespace))
                {
                    containingNamespace.Accept(this.NotFirstVisitor);
                    AddPunctuation(containingNamespace.IsGlobalNamespace ? SyntaxKind.ColonColonToken : SyntaxKind.DotToken);
                }
            }
 
            if (symbol.IsGlobalNamespace)
            {
                AddGlobalNamespace(symbol);
            }
            else
            {
                builder.Add(CreatePart(SymbolDisplayPartKind.NamespaceName, symbol, symbol.Name));
            }
        }
 
        private void AddGlobalNamespace(INamespaceSymbol globalNamespace)
        {
            // Formerly, localized via MessageID.IDS_GlobalNamespace.
            const string standaloneGlobalNamespaceString = "<global namespace>";
 
            switch (format.GlobalNamespaceStyle)
            {
                case SymbolDisplayGlobalNamespaceStyle.Omitted:
                    break;
                case SymbolDisplayGlobalNamespaceStyle.Included:
                    if (this.isFirstSymbolVisited)
                    {
                        builder.Add(CreatePart(
                            SymbolDisplayPartKind.Text,
                            globalNamespace,
                            standaloneGlobalNamespaceString));
                    }
                    else
                    {
                        builder.Add(CreatePart(SymbolDisplayPartKind.Keyword, globalNamespace,
                            SyntaxFacts.GetText(SyntaxKind.GlobalKeyword)));
                    }
                    break;
                case SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining:
                    Debug.Assert(this.isFirstSymbolVisited, "Don't call with IsFirstSymbolVisited = false if OmittedAsContaining");
                    builder.Add(CreatePart(
                        SymbolDisplayPartKind.Text,
                        globalNamespace,
                        standaloneGlobalNamespaceString));
                    break;
                default:
                    throw ExceptionUtilities.UnexpectedValue(format.GlobalNamespaceStyle);
            }
        }
 
        public override void VisitLocal(ILocalSymbol symbol)
        {
            if (format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeModifiers))
            {
                if (symbol.IsRef)
                {
                    if (symbol.ScopedKind == ScopedKind.ScopedRef)
                    {
                        AddKeyword(SyntaxKind.ScopedKeyword);
                        AddSpace();
                    }
 
                    AddKeyword(SyntaxKind.RefKeyword);
                    AddSpace();
 
                    if (symbol.RefKind == RefKind.RefReadOnly)
                    {
                        AddKeyword(SyntaxKind.ReadOnlyKeyword);
                        AddSpace();
                    }
                }
                else if (symbol.ScopedKind == ScopedKind.ScopedValue)
                {
                    AddKeyword(SyntaxKind.ScopedKeyword);
                    AddSpace();
                }
            }
 
            if (format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeType))
            {
                symbol.Type.Accept(this.NotFirstVisitor);
                AddSpace();
            }
 
            if (symbol.IsConst)
            {
                builder.Add(CreatePart(SymbolDisplayPartKind.ConstantName, symbol, symbol.Name));
            }
            else
            {
                builder.Add(CreatePart(SymbolDisplayPartKind.LocalName, symbol, symbol.Name));
            }
 
            if (format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeConstantValue) &&
                symbol.IsConst &&
                symbol.HasConstantValue &&
                CanAddConstant(symbol.Type, symbol.ConstantValue))
            {
                AddSpace();
                AddPunctuation(SyntaxKind.EqualsToken);
                AddSpace();
 
                AddConstantValue(symbol.Type, symbol.ConstantValue);
            }
        }
 
        public override void VisitDiscard(IDiscardSymbol symbol)
        {
            if (format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeType))
            {
                symbol.Type.Accept(this.NotFirstVisitor);
                AddSpace();
            }
 
            builder.Add(CreatePart(SymbolDisplayPartKind.Punctuation, symbol, "_"));
        }
 
        public override void VisitRangeVariable(IRangeVariableSymbol symbol)
        {
            if (format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeType))
            {
                ITypeSymbol type = GetRangeVariableType(symbol);
 
                if (type != null && type.TypeKind != TypeKind.Error)
                {
                    type.Accept(this);
                }
                else
                {
                    builder.Add(CreatePart(SymbolDisplayPartKind.ErrorTypeName, type, "?"));
                }
 
                AddSpace();
            }
 
            builder.Add(CreatePart(SymbolDisplayPartKind.RangeVariableName, symbol, symbol.Name));
        }
 
        public override void VisitLabel(ILabelSymbol symbol)
        {
            builder.Add(CreatePart(SymbolDisplayPartKind.LabelName, symbol, symbol.Name));
        }
 
        public override void VisitAlias(IAliasSymbol symbol)
        {
            builder.Add(CreatePart(SymbolDisplayPartKind.AliasName, symbol, symbol.Name));
 
            if (format.LocalOptions.IncludesOption(SymbolDisplayLocalOptions.IncludeType))
            {
                // ???
                AddPunctuation(SyntaxKind.EqualsToken);
                symbol.Target.Accept(this);
            }
        }
 
        protected override void AddSpace()
        {
            builder.Add(CreatePart(SymbolDisplayPartKind.Space, null, " "));
        }
 
        private void AddPunctuation(SyntaxKind punctuationKind)
        {
            builder.Add(CreatePart(SymbolDisplayPartKind.Punctuation, null, SyntaxFacts.GetText(punctuationKind)));
        }
 
        private void AddKeyword(SyntaxKind keywordKind)
        {
            builder.Add(CreatePart(SymbolDisplayPartKind.Keyword, null, SyntaxFacts.GetText(keywordKind)));
        }
 
        private void AddAccessibilityIfNeeded(ISymbol symbol)
        {
            INamedTypeSymbol containingType = symbol.ContainingType;
 
            // this method is only called for members and they should have a containingType or a containing symbol should be a TypeSymbol.
            Debug.Assert((object)containingType != null || (symbol.ContainingSymbol is ITypeSymbol));
 
            if (format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeAccessibility) &&
                (containingType == null ||
                 (containingType.TypeKind != TypeKind.Interface && !IsEnumMember(symbol) & !IsLocalFunction(symbol))))
            {
                AddAccessibility(symbol);
            }
        }
 
        private static bool IsLocalFunction(ISymbol symbol)
        {
            if (symbol.Kind != SymbolKind.Method)
            {
                return false;
            }
 
            return ((IMethodSymbol)symbol).MethodKind == MethodKind.LocalFunction;
        }
 
        private void AddAccessibility(ISymbol symbol)
        {
            switch (symbol.DeclaredAccessibility)
            {
                case Accessibility.Private:
                    AddKeyword(SyntaxKind.PrivateKeyword);
                    break;
                case Accessibility.Internal:
                    AddKeyword(SyntaxKind.InternalKeyword);
                    break;
                case Accessibility.ProtectedAndInternal:
                    AddKeyword(SyntaxKind.PrivateKeyword);
                    AddSpace();
                    AddKeyword(SyntaxKind.ProtectedKeyword);
                    break;
                case Accessibility.Protected:
                    AddKeyword(SyntaxKind.ProtectedKeyword);
                    break;
                case Accessibility.ProtectedOrInternal:
                    AddKeyword(SyntaxKind.ProtectedKeyword);
                    AddSpace();
                    AddKeyword(SyntaxKind.InternalKeyword);
                    break;
                case Accessibility.Public:
                    AddKeyword(SyntaxKind.PublicKeyword);
                    break;
                default:
                    throw ExceptionUtilities.UnexpectedValue(symbol.DeclaredAccessibility);
            }
 
            AddSpace();
        }
 
        private bool ShouldVisitNamespace(ISymbol containingSymbol)
        {
            var namespaceSymbol = containingSymbol as INamespaceSymbol;
            if (namespaceSymbol == null)
            {
                return false;
            }
 
            if (format.TypeQualificationStyle != SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces)
            {
                return false;
            }
 
            return
                !namespaceSymbol.IsGlobalNamespace ||
                format.GlobalNamespaceStyle == SymbolDisplayGlobalNamespaceStyle.Included;
        }
 
        private bool IncludeNamedType(INamedTypeSymbol namedType)
        {
            if (namedType is null)
            {
                return false;
            }
 
            if (namedType.IsScriptClass && !format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.IncludeScriptType))
            {
                return false;
            }
 
            if (namedType == semanticModelOpt?.Compilation.ScriptGlobalsType)
            {
                return false;
            }
 
            return true;
        }
 
        private static bool IsEnumMember(ISymbol symbol)
        {
            return symbol != null
                && symbol.Kind == SymbolKind.Field
                && symbol.ContainingType != null
                && symbol.ContainingType.TypeKind == TypeKind.Enum
                && symbol.Name != WellKnownMemberNames.EnumBackingFieldName;
        }
    }
}