File: CSharp\SymbolExtensions.cs
Web Access
Project: src\src\Razor\src\Compiler\Microsoft.CodeAnalysis.Razor.Compiler\src\Microsoft.CodeAnalysis.Razor.Compiler.csproj (Microsoft.CodeAnalysis.Razor.Compiler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Razor.Language;
 
namespace Microsoft.CodeAnalysis.Razor;
 
internal static class SymbolExtensions
{
    /// <summary>
    /// Converts the symbol to a string representation. This is the equivalent of calling
    /// ISymbol.ToDisplayString()
    /// </summary>
    /// <returns>A formatted string representation of the symbol.</returns>
    internal static string GetDefaultDisplayString(this ISymbol typeSymbol)
        => SymbolCache.GetSymbolData(typeSymbol).GetDefaultDisplayString();
 
    /// <summary>
    /// Converts the symbol to a string representation. This is the equivalent of calling
    /// ISymbol.ToDisplayString(WellKnownSymbolDisplayFormats.FullNameTypeDisplayFormat)
    /// </summary>
    /// <returns>A formatted string representation of the symbol.</returns>
    internal static string GetFullName(this ISymbol typeSymbol)
        => SymbolCache.GetSymbolData(typeSymbol).GetFullName();
 
    /// <summary>
    /// Converts the symbol to a string representation. This is the equivalent of calling
    /// ISymbol.ToDisplayString(WellKnownSymbolDisplayFormats.GloballyQualifiedFullNameTypeDisplayFormat)
    /// </summary>
    /// <returns>A formatted string representation of the symbol.</returns>
    internal static string GetGloballyQualifiedFullName(this ISymbol typeSymbol)
        => SymbolCache.GetSymbolData(typeSymbol).GetGloballyQualifiedFullName();
 
    internal static bool HasFullName(this AttributeData attribute, string fullName)
        => attribute.AttributeClass is { } attributeClass && attributeClass.HasFullName(fullName);
 
    internal static bool TryGetAttribute(
        this ISymbol symbol, string fullName, [NotNullWhen(true)] out AttributeData? result)
        => TryGetAttribute(symbol.GetAttributes(), fullName, out result);
 
    private static bool TryGetAttribute(ImmutableArray<AttributeData> attributes, string fullName, [NotNullWhen(true)] out AttributeData? result)
    {
        result = GetAttribute(attributes, fullName);
        return result != null;
    }
 
    internal static AttributeData? GetAttribute(this ISymbol symbol, string fullName)
        => GetAttribute(symbol.GetAttributes(), fullName);
 
    private static AttributeData? GetAttribute(ImmutableArray<AttributeData> attributes, string fullName)
    {
        foreach (var attribute in attributes)
        {
            if (attribute.HasFullName(fullName))
            {
                return attribute;
            }
        }
 
        return null;
    }
 
    /// <summary>
    /// Checks if <paramref name="typeSymbol"/> has the same fully qualified name as <paramref name="fullName"/>.
    /// </summary>
    /// <remarks>
    /// This is a *very* simple implementation that doesn't handle all cases. It's only intended to be used
    /// for a small set of known types. If you add a new type to be looked up by this function you must ensure
    /// the simple check works correctly for it.
    /// </remarks>
    /// <returns>true if the type has the specified full name</returns>
    internal static bool HasFullName(this ITypeSymbol typeSymbol, string fullName)
    {
        // generally we expect this call to fail, so try and match on the simple name first, before
        // checking the fully qualified name, to prevent unnecessary string allocation from ToDisplayString
        if (typeSymbol.Name.Length > fullName.Length)
        {
            return false;
        }
 
        var fullNameSpan = fullName.AsSpan();
 
        // The simple name doesn't include generic type params, so skip over them in the full name if they are present.
        // UNDONE: This will need adjustment if this method should need to handle types overloaded on the number
        // of generic type parameters.
        if (fullNameSpan[^1] == '>')
        {
            var endIndex = fullNameSpan.LastIndexOf('<');
            Debug.Assert(endIndex >= 0, "Symbol name ends in '<' but does not contain '>'!");
            fullNameSpan = fullNameSpan[..endIndex];
        }
 
        // The strategy here is to walk backward through the various simple names in the full name and test
        // them against the symbol parent chain.
 
        for (ISymbol symbol = typeSymbol; !IsGlobalNamespace(symbol); symbol = symbol.ContainingSymbol)
        {
            var symbolNameSpan = symbol.Name.AsSpan();
 
            // If the full name span doesn't end with the current symbol name, this isn't a match.
            if (!fullNameSpan.EndsWith(symbolNameSpan, StringComparison.Ordinal))
            {
                return false;
            }
 
            fullNameSpan = fullNameSpan[..^symbolNameSpan.Length];
 
            if (fullNameSpan.Length == 0)
            {
                // We just completed testing against full name.
                // Check to see if the parent symbol is the global namespace. If it is,
                // we're at the last symbol that we need to check and have a match!
                // Otherwise, this match failed.
                return IsGlobalNamespace(symbol.ContainingSymbol);
            }
 
            // Check the previous character. If it isn't a '.', we don't have a match.
            // UNDONE: This will need adjustment if this method is expected to work properly with
            // nested types using a '+' character.
            if (fullNameSpan[^1] != '.')
            {
                return false;
            }
 
            // Skip the dot and loop to check the parent symbol.
            fullNameSpan = fullNameSpan[..^1];
        }
 
        return false;
 
        static bool IsGlobalNamespace(ISymbol symbol)
        {
            return symbol is INamespaceSymbol { IsGlobalNamespace: true };
        }
    }
}