File: DocumentationComments\DocumentationCommentIDVisitor.PartVisitor.cs
Web Access
Project: src\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.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using System;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal sealed partial class DocumentationCommentIDVisitor
    {
        /// <summary>
        /// A visitor that generates the part of the documentation comment after the initial type
        /// and colon.
        /// </summary>
        private sealed class PartVisitor : CSharpSymbolVisitor<StringBuilder, object>
        {
            // Everyone outside this type uses this one.
            internal static readonly PartVisitor Instance = new PartVisitor(inParameterOrReturnType: false);
 
            // Select callers within this type use this one.
            private static readonly PartVisitor s_parameterOrReturnTypeInstance = new PartVisitor(inParameterOrReturnType: true);
 
            private readonly bool _inParameterOrReturnType;
 
            private PartVisitor(bool inParameterOrReturnType)
            {
                _inParameterOrReturnType = inParameterOrReturnType;
            }
 
            public override object VisitArrayType(ArrayTypeSymbol symbol, StringBuilder builder)
            {
                Visit(symbol.ElementType, builder);
 
                // Rank-one arrays are displayed different than rectangular arrays
                if (symbol.IsSZArray)
                {
                    builder.Append("[]");
                }
                else
                {
                    builder.Append("[0:");
 
                    for (int i = 0; i < symbol.Rank - 1; i++)
                    {
                        builder.Append(",0:");
                    }
 
                    builder.Append(']');
                }
 
                return null;
            }
 
            public override object VisitField(FieldSymbol symbol, StringBuilder builder)
            {
                Visit(symbol.ContainingType, builder);
                builder.Append('.');
                builder.Append(symbol.Name);
 
                return null;
            }
 
            private void VisitParameters(ImmutableArray<ParameterSymbol> parameters, bool isVararg, StringBuilder builder)
            {
                builder.Append('(');
                bool needsComma = false;
 
                foreach (var parameter in parameters)
                {
                    if (needsComma)
                    {
                        builder.Append(',');
                    }
 
                    Visit(parameter, builder);
                    needsComma = true;
                }
 
                if (isVararg && needsComma)
                {
                    builder.Append(',');
                }
 
                builder.Append(')');
            }
 
            public override object VisitMethod(MethodSymbol symbol, StringBuilder builder)
            {
                Visit(symbol.ContainingType, builder);
                builder.Append('.');
                builder.Append(GetEscapedMetadataName(symbol));
 
                if (symbol.Arity != 0)
                {
                    builder.Append("``");
                    builder.Append(symbol.Arity);
                }
 
                if (symbol.Parameters.Any() || symbol.IsVararg)
                {
                    s_parameterOrReturnTypeInstance.VisitParameters(symbol.Parameters, symbol.IsVararg, builder);
                }
 
                if (symbol.MethodKind == MethodKind.Conversion)
                {
                    builder.Append('~');
                    s_parameterOrReturnTypeInstance.Visit(symbol.ReturnType, builder);
                }
 
                return null;
            }
 
            public override object VisitProperty(PropertySymbol symbol, StringBuilder builder)
            {
                Visit(symbol.ContainingType, builder);
                builder.Append('.');
                builder.Append(GetEscapedMetadataName(symbol));
 
                if (symbol.Parameters.Any())
                {
                    s_parameterOrReturnTypeInstance.VisitParameters(symbol.Parameters, false, builder);
                }
 
                return null;
            }
 
            public override object VisitEvent(EventSymbol symbol, StringBuilder builder)
            {
                Visit(symbol.ContainingType, builder);
                builder.Append('.');
                builder.Append(GetEscapedMetadataName(symbol));
 
                return null;
            }
 
            public override object VisitTypeParameter(TypeParameterSymbol symbol, StringBuilder builder)
            {
                int ordinalOffset = 0;
 
                // Is this a type parameter on a type?
                Symbol containingSymbol = symbol.ContainingSymbol;
                if (containingSymbol.Kind == SymbolKind.Method)
                {
                    builder.Append("``");
                }
                else
                {
                    Debug.Assert(containingSymbol is NamedTypeSymbol);
 
                    // If the containing type is nested within other types, then we need to add their arities.
                    // e.g. A<T>.B<U>.M<V>(T t, U u, V v) should be M(`0, `1, ``0).
                    for (NamedTypeSymbol curr = containingSymbol.ContainingType; (object)curr != null; curr = curr.ContainingType)
                    {
                        ordinalOffset += curr.Arity;
                    }
                    builder.Append('`');
                }
 
                builder.Append(symbol.Ordinal + ordinalOffset);
 
                return null;
            }
 
            public override object VisitNamedType(NamedTypeSymbol symbol, StringBuilder builder)
            {
                if ((object)symbol.ContainingSymbol != null && symbol.ContainingSymbol.Name.Length != 0)
                {
                    Visit(symbol.ContainingSymbol, builder);
                    builder.Append('.');
                }
 
                builder.Append(symbol.Name);
 
                if (symbol.Arity != 0)
                {
                    // Special case: dev11 treats types instances of the declaring type in the parameter list
                    // (and return type, for conversions) as constructed with its own type parameters.
                    if (!_inParameterOrReturnType && TypeSymbol.Equals(symbol, symbol.ConstructedFrom, TypeCompareKind.AllIgnoreOptions))
                    {
                        builder.Append('`');
                        builder.Append(symbol.Arity);
                    }
                    else
                    {
                        builder.Append('{');
 
                        bool needsComma = false;
 
                        foreach (var typeArgument in symbol.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics)
                        {
                            if (needsComma)
                            {
                                builder.Append(',');
                            }
 
                            Visit(typeArgument.Type, builder);
 
                            needsComma = true;
                        }
 
                        builder.Append('}');
                    }
                }
 
                return null;
            }
 
            public override object VisitPointerType(PointerTypeSymbol symbol, StringBuilder builder)
            {
                Visit(symbol.PointedAtType, builder);
                builder.Append('*');
 
                return null;
            }
 
            public override object VisitNamespace(NamespaceSymbol symbol, StringBuilder builder)
            {
                if ((object)symbol.ContainingNamespace != null && symbol.ContainingNamespace.Name.Length != 0)
                {
                    Visit(symbol.ContainingNamespace, builder);
                    builder.Append('.');
                }
 
                builder.Append(symbol.Name);
 
                return null;
            }
 
            public override object VisitParameter(ParameterSymbol symbol, StringBuilder builder)
            {
                Debug.Assert(_inParameterOrReturnType);
 
                Visit(symbol.Type, builder);
 
                // ref and out params are suffixed with @
                if (symbol.RefKind != RefKind.None)
                {
                    builder.Append('@');
                }
 
                return null;
            }
 
            public override object VisitErrorType(ErrorTypeSymbol symbol, StringBuilder builder)
            {
                return VisitNamedType(symbol, builder);
            }
 
            public override object VisitDynamicType(DynamicTypeSymbol symbol, StringBuilder builder)
            {
                // NOTE: this is a change from dev11, which did not allow dynamic in parameter types.
                // If we wanted to be really conservative, we would actually visit the symbol for
                // System.Object.  However, the System.Object type must always have exactly this
                // doc comment ID, so the hassle seems unjustifiable.
                builder.Append("System.Object");
 
                return null;
            }
 
            private static string GetEscapedMetadataName(Symbol symbol)
            {
                string metadataName = symbol.MetadataName;
 
                int colonColonIndex = metadataName.IndexOf("::", StringComparison.Ordinal);
                int startIndex = colonColonIndex < 0 ? 0 : colonColonIndex + 2;
 
                PooledStringBuilder pooled = PooledStringBuilder.GetInstance();
                pooled.Builder.Append(metadataName, startIndex, metadataName.Length - startIndex);
                pooled.Builder.Replace('.', '#').Replace('<', '{').Replace('>', '}');
                return pooled.ToStringAndFree();
            }
        }
    }
}