File: TestAttributesVisitor.cs
Web Access
Project: src\src\Compilers\Test\Utilities\CSharp\Microsoft.CodeAnalysis.CSharp.Test.Utilities.csproj (Microsoft.CodeAnalysis.CSharp.Test.Utilities)
// 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.
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.Test.Utilities
{
    internal abstract class TestAttributesVisitor : CSharpSymbolVisitor
    {
        protected readonly StringBuilder _builder;
        protected readonly HashSet<Symbol> _reported;
 
        protected TestAttributesVisitor(StringBuilder builder)
        {
            _builder = builder;
            _reported = new HashSet<Symbol>();
        }
 
        public override void DefaultVisit(Symbol symbol)
        {
            ReportSymbol(symbol);
        }
 
        public override void VisitModule(ModuleSymbol module)
        {
            Visit(module.GlobalNamespace);
        }
 
        public override void VisitNamespace(NamespaceSymbol @namespace)
        {
            VisitList(@namespace.GetMembers());
        }
 
        public override void VisitNamedType(NamedTypeSymbol type)
        {
            ReportSymbol(type);
            VisitList(type.TypeParameters);
 
            foreach (var member in type.GetMembers())
            {
                // Skip accessors since those are covered by associated symbol.
                if (member.IsAccessor()) continue;
                Visit(member);
            }
        }
 
        public override void VisitMethod(MethodSymbol method)
        {
            ReportSymbol(method);
            VisitList(method.TypeParameters);
            VisitList(method.Parameters);
        }
 
        public override void VisitEvent(EventSymbol @event)
        {
            ReportSymbol(@event);
            Visit(@event.AddMethod);
            Visit(@event.RemoveMethod);
        }
 
        public override void VisitProperty(PropertySymbol property)
        {
            ReportSymbol(property);
            VisitList(property.Parameters);
            Visit(property.GetMethod);
            Visit(property.SetMethod);
        }
 
        public override void VisitTypeParameter(TypeParameterSymbol typeParameter)
        {
            ReportSymbol(typeParameter);
        }
 
        private void VisitList<TSymbol>(ImmutableArray<TSymbol> symbols) where TSymbol : Symbol
        {
            foreach (var symbol in symbols)
            {
                Visit(symbol);
            }
        }
 
        /// <summary>
        /// Return the containing symbol used in the hierarchy here. Specifically, the
        /// hierarchy contains types, members, and parameters only, and accessors are
        /// considered members of the associated symbol rather than the type.
        /// </summary>
        private static Symbol? GetContainingSymbol(Symbol symbol)
        {
            if (symbol.IsAccessor())
            {
                return ((MethodSymbol)symbol).AssociatedSymbol;
            }
            var containingSymbol = symbol.ContainingSymbol;
            return containingSymbol?.Kind == SymbolKind.Namespace ? null : containingSymbol;
        }
 
        protected static string GetIndentString(Symbol symbol)
        {
            int level = 0;
            var current = symbol;
            while (true)
            {
                current = GetContainingSymbol(current);
                if (current is null)
                {
                    break;
                }
                level++;
            }
            return new string(' ', level * 4);
        }
 
        protected abstract SymbolDisplayFormat DisplayFormat { get; }
 
        protected void ReportContainingSymbols(Symbol symbol)
        {
            var s = GetContainingSymbol(symbol);
            if (s is null)
            {
                return;
            }
            if (_reported.Contains(s))
            {
                return;
            }
            ReportContainingSymbols(s);
            _builder.Append(GetIndentString(s));
            _builder.AppendLine(s.ToDisplayString(DisplayFormat));
            _reported.Add(s);
        }
 
        protected virtual void ReportSymbol(Symbol symbol)
        {
            var type = (symbol as TypeSymbol) ?? symbol.GetTypeOrReturnType().Type;
            var attribute = GetTargetAttribute((symbol is MethodSymbol method) ? method.GetReturnTypeAttributes() : symbol.GetAttributes());
            Debug.Assert((!TypeRequiresAttribute(type)) || (attribute != null));
            if (attribute == null)
            {
                return;
            }
            ReportContainingSymbols(symbol);
            _builder.Append(GetIndentString(symbol));
            _builder.Append($"{ReportAttribute(attribute)} ");
            _builder.AppendLine(symbol.ToDisplayString(DisplayFormat));
            _reported.Add(symbol);
        }
 
        protected static string ReportAttribute(CSharpAttributeData attribute)
        {
            var builder = new StringBuilder();
            builder.Append('[');
 
            Assert.NotNull(attribute.AttributeClass);
            var name = attribute.AttributeClass!.Name;
            if (name.EndsWith("Attribute")) name = name.Substring(0, name.Length - 9);
            builder.Append(name);
 
            var arguments = attribute.ConstructorArguments.ToImmutableArray();
            if (arguments.Length > 0)
            {
                builder.Append('(');
                printValues(builder, arguments);
                builder.Append(')');
            }
 
            builder.Append(']');
            return builder.ToString();
 
            static void printValues(StringBuilder builder, ImmutableArray<TypedConstant> values)
            {
                for (int i = 0; i < values.Length; i++)
                {
                    if (i > 0)
                    {
                        builder.Append(", ");
                    }
                    printValue(builder, values[i]);
                }
            }
 
            static void printValue(StringBuilder builder, TypedConstant value)
            {
                if (value.Kind == TypedConstantKind.Array)
                {
                    builder.Append("{ ");
                    printValues(builder, value.Values);
                    builder.Append(" }");
                }
                else
                {
                    builder.Append(value.Value);
                }
            }
        }
 
        protected abstract bool TypeRequiresAttribute(TypeSymbol? type);
 
        protected abstract CSharpAttributeData? GetTargetAttribute(ImmutableArray<CSharpAttributeData> attributes);
 
        protected static CSharpAttributeData? GetAttribute(ImmutableArray<CSharpAttributeData> attributes, string namespaceName, string name)
        {
            foreach (var attribute in attributes)
            {
                Assert.NotNull(attribute.AttributeConstructor);
                var containingType = attribute.AttributeConstructor!.ContainingType;
                if (containingType.Name == name && containingType.ContainingNamespace.QualifiedName == namespaceName)
                {
                    return attribute;
                }
            }
            return null;
        }
    }
}