| File: CSharp\SymbolExtensions.cs | Web Access |
| Project: src\roslyn\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 }; } } }