File: Tooltip\BoundAttributeDescriptionInfo.cs
Web Access
Project: src\src\Razor\src\Razor\src\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj (Microsoft.CodeAnalysis.Razor.Workspaces)
// 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 Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.Language;
 
namespace Microsoft.CodeAnalysis.Razor.Tooltip;
 
internal record BoundAttributeDescriptionInfo(string ReturnTypeName, string TypeName, string PropertyName, string? Documentation = null)
{
    public static BoundAttributeDescriptionInfo From(BoundAttributeParameterDescriptor parameter)
    {
        ArgHelper.ThrowIfNull(parameter);
 
        var parentTagHelperTypeName = parameter.Parent.Parent.TypeName;
        var propertyName = parameter.PropertyName;
 
        return new BoundAttributeDescriptionInfo(
            parameter.TypeName,
            parentTagHelperTypeName,
            propertyName,
            parameter.Documentation);
    }
 
    public static BoundAttributeDescriptionInfo From(BoundAttributeDescriptor boundAttribute, bool isIndexer)
        => From(boundAttribute, isIndexer, parentTagHelperTypeName: null);
 
    public static BoundAttributeDescriptionInfo From(BoundAttributeDescriptor boundAttribute, bool isIndexer, string? parentTagHelperTypeName)
    {
        if (boundAttribute is null)
        {
            throw new ArgumentNullException(nameof(boundAttribute));
        }
 
        var returnTypeName = isIndexer ? boundAttribute.IndexerTypeName : boundAttribute.TypeName;
        var propertyName = boundAttribute.PropertyName;
 
        // The BoundAttributeDescriptor does not directly have the TagHelperTypeName information available.
        // Because of this we need to resolve it from other parts of it.
        parentTagHelperTypeName ??= ResolveTagHelperTypeName(propertyName, boundAttribute.DisplayName);
 
        return new BoundAttributeDescriptionInfo(
            returnTypeName.AssumeNotNull(),
            parentTagHelperTypeName,
            propertyName,
            boundAttribute.Documentation);
    }
 
    // Internal for testing
    internal static string ResolveTagHelperTypeName(string propertyName, string? displayName)
    {
        // A BoundAttributeDescriptor does not have a direct reference to its parent TagHelper.
        // However, when it was constructed the parent TagHelper's type name was embedded into
        // its DisplayName. In VSCode we can't use the DisplayName verbatim for descriptions
        // because the DisplayName is typically too long to display properly. Therefore we need
        // to break it apart and then reconstruct it in a reduced format.
        // i.e. this is the format the display name comes in:
        // ReturnTypeName SomeTypeName.SomePropertyName
        //
        // See DefaultBoundAttributeDescriptorBuilder.GetDisplayName() for added detail.
 
        var displayNameSpan = displayName.AsSpanOrDefault();
 
        // Search for the first space, which should be immediately after the return type.
        var spaceIndex = displayNameSpan.IndexOf(' ');
        if (spaceIndex < 0)
        {
            return string.Empty;
        }
 
        // Increment by one to skip over the space.
        displayNameSpan = displayNameSpan[(spaceIndex + 1)..];
 
        var propertyNameSpan = propertyName.AsSpanOrDefault();
 
        // Strip off the trailing property name.
        if (displayNameSpan.EndsWith(propertyNameSpan, StringComparison.Ordinal))
        {
            displayNameSpan = displayNameSpan[..^propertyNameSpan.Length];
        }
 
        // Strip off the trailing '.'
        if (displayNameSpan is [.. var start, '.'])
        {
            displayNameSpan = start;
        }
 
        return displayNameSpan.ToString();
    }
}