File: Language\TagHelperDescriptor.cs
Web Access
Project: src\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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.AspNetCore.Razor.Utilities;
using Microsoft.CodeAnalysis;

namespace Microsoft.AspNetCore.Razor.Language;

[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
public sealed class TagHelperDescriptor : TagHelperObject<TagHelperDescriptor>
{
    private readonly TagHelperFlags _flags;

    private ImmutableArray<BoundAttributeDescriptor> _editorRequiredAttributes;

    public TagHelperFlags Flags => _flags;
    public TagHelperKind Kind { get; }
    public RuntimeKind RuntimeKind { get; }

    public string Name { get; }
    public string AssemblyName { get; }

    public string TypeName => TypeNameObject.FullName.AssumeNotNull();
    public string? TypeNamespace => TypeNameObject.Namespace;
    public string? TypeNameIdentifier => TypeNameObject.Name;

    public string? Documentation => DocumentationObject.GetText();

    internal TypeNameObject TypeNameObject { get; }
    internal DocumentationObject DocumentationObject { get; }

    public string DisplayName { get; }
    public string? TagOutputHint { get; }

    public bool CaseSensitive => _flags.IsFlagSet(TagHelperFlags.CaseSensitive);

    public ImmutableArray<AllowedChildTagDescriptor> AllowedChildTags { get; }
    public ImmutableArray<BoundAttributeDescriptor> BoundAttributes { get; }
    public ImmutableArray<TagMatchingRuleDescriptor> TagMatchingRules { get; }

    public MetadataObject Metadata { get; }

    /// <summary>
    /// Gets whether the component matches a tag with a fully qualified name.
    /// </summary>
    internal bool IsFullyQualifiedNameMatch => _flags.IsFlagSet(TagHelperFlags.IsFullyQualifiedNameMatch);

    public bool ClassifyAttributesOnly => _flags.IsFlagSet(TagHelperFlags.ClassifyAttributesOnly);

    internal TagHelperDescriptor(
        TagHelperFlags flags,
        TagHelperKind kind,
        RuntimeKind runtimeKind,
        string name,
        string assemblyName,
        string displayName,
        TypeNameObject typeNameObject,
        DocumentationObject documentationObject,
        string? tagOutputHint,
        ImmutableArray<TagMatchingRuleDescriptor> tagMatchingRules,
        ImmutableArray<BoundAttributeDescriptor> attributeDescriptors,
        ImmutableArray<AllowedChildTagDescriptor> allowedChildTags,
        MetadataObject metadata,
        ImmutableArray<RazorDiagnostic> diagnostics)
        : base(diagnostics)
    {
        _flags = flags;
        RuntimeKind = runtimeKind;
        Kind = kind;
        Name = name;
        AssemblyName = assemblyName;
        DisplayName = displayName;
        TypeNameObject = typeNameObject;
        DocumentationObject = documentationObject;
        TagOutputHint = tagOutputHint;
        TagMatchingRules = tagMatchingRules.NullToEmpty();
        BoundAttributes = attributeDescriptors.NullToEmpty();
        AllowedChildTags = allowedChildTags.NullToEmpty();
        Metadata = metadata;

        foreach (var tagMatchingRule in TagMatchingRules)
        {
            tagMatchingRule.SetParent(this);
        }

        foreach (var boundAttribute in BoundAttributes)
        {
            boundAttribute.SetParent(this);
        }

        foreach (var allowedChildTag in AllowedChildTags)
        {
            allowedChildTag.SetParent(this);
        }
    }

    private protected override void BuildChecksum(in Checksum.Builder builder)
    {
        builder.Append((byte)Flags);
        builder.Append((byte)Kind);
        builder.Append((byte)RuntimeKind);
        builder.Append(Name);
        builder.Append(AssemblyName);
        builder.Append(DisplayName);
        builder.Append(TagOutputHint);

        TypeNameObject.AppendToChecksum(in builder);
        DocumentationObject.AppendToChecksum(in builder);

        foreach (var descriptor in AllowedChildTags)
        {
            builder.Append(descriptor.Checksum);
        }

        foreach (var descriptor in BoundAttributes)
        {
            builder.Append(descriptor.Checksum);
        }

        foreach (var descriptor in TagMatchingRules)
        {
            builder.Append(descriptor.Checksum);
        }

        Metadata.AppendToChecksum(in builder);
    }

    internal ImmutableArray<BoundAttributeDescriptor> EditorRequiredAttributes
    {
        get
        {
            if (_editorRequiredAttributes.IsDefault)
            {
                ImmutableInterlocked.InterlockedInitialize(ref _editorRequiredAttributes, GetEditorRequiredAttributes(BoundAttributes));
            }

            return _editorRequiredAttributes;

            static ImmutableArray<BoundAttributeDescriptor> GetEditorRequiredAttributes(ImmutableArray<BoundAttributeDescriptor> attributes)
            {
                if (attributes.Length == 0)
                {
                    return ImmutableArray<BoundAttributeDescriptor>.Empty;
                }

                using var results = new PooledArrayBuilder<BoundAttributeDescriptor>(capacity: attributes.Length);

                foreach (var attribute in attributes)
                {
                    if (attribute is { IsEditorRequired: true } editorRequiredAttribute)
                    {
                        results.Add(editorRequiredAttribute);
                    }
                }

                return results.ToImmutableAndClear();
            }
        }
    }

    public IEnumerable<RazorDiagnostic> GetAllDiagnostics()
    {
        using var diagnostics = new PooledArrayBuilder<RazorDiagnostic>();

        AppendAllDiagnostics(ref diagnostics.AsRef());

        foreach (var diagnostic in diagnostics)
        {
            yield return diagnostic;
        }
    }

    internal void AppendAllDiagnostics(ref PooledArrayBuilder<RazorDiagnostic> diagnostics)
    {
        foreach (var allowedChildTag in AllowedChildTags)
        {
            diagnostics.AddRange(allowedChildTag.Diagnostics);
        }

        foreach (var boundAttribute in BoundAttributes)
        {
            diagnostics.AddRange(boundAttribute.Diagnostics);
        }

        foreach (var tagMatchingRule in TagMatchingRules)
        {
            diagnostics.AddRange(tagMatchingRule.Diagnostics);
        }

        diagnostics.AddRange(Diagnostics);
    }

    public override string ToString()
    {
        return DisplayName ?? base.ToString()!;
    }

    private string GetDebuggerDisplay()
    {
        return $"{DisplayName} - {string.Join(" | ", TagMatchingRules.Select(r => r.GetDebuggerDisplay()))}";
    }

    internal TagHelperDescriptor WithName(string name)
    {
        return new(
            Flags, Kind, RuntimeKind, name, AssemblyName, DisplayName,
            TypeNameObject, DocumentationObject, TagOutputHint,
            TagMatchingRules, BoundAttributes, AllowedChildTags,
            Metadata, Diagnostics);
    }
}