File: HtmlFacts.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 System.Collections.Frozen;
using System.Collections.Immutable;
using Microsoft.AspNetCore.Razor.Language.Syntax;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.VisualStudio.Editor.Razor;
 
internal static class HtmlFacts
{
    private static readonly FrozenSet<string> s_htmlSchemaTagNames = new string[]
    {
        "DOCTYPE",
        "a",
        "abbr",
        "acronym",
        "address",
        "applet",
        "area",
        "article",
        "aside",
        "audio",
        "b",
        "base",
        "basefont",
        "bdi",
        "bdo",
        "big",
        "blockquote",
        "body",
        "br",
        "button",
        "canvas",
        "caption",
        "center",
        "cite",
        "code",
        "col",
        "colgroup",
        "data",
        "datalist",
        "dd",
        "del",
        "details",
        "dfn",
        "dialog",
        "dir",
        "div",
        "dl",
        "dt",
        "em",
        "embed",
        "fieldset",
        "figcaption",
        "figure",
        "font",
        "footer",
        "form",
        "frame",
        "frameset",
        "h1",
        "h2",
        "h3",
        "h4",
        "h5",
        "h6",
        "head",
        "header",
        "hr",
        "html",
        "i",
        "iframe",
        "img",
        "input",
        "ins",
        "kbd",
        "label",
        "legend",
        "li",
        "link",
        "main",
        "map",
        "mark",
        "meta",
        "meter",
        "nav",
        "noframes",
        "noscript",
        "object",
        "ol",
        "optgroup",
        "option",
        "output",
        "p",
        "param",
        "picture",
        "pre",
        "progress",
        "q",
        "rp",
        "rt",
        "ruby",
        "s",
        "samp",
        "script",
        "section",
        "select",
        "small",
        "source",
        "span",
        "strike",
        "strong",
        "style",
        "sub",
        "summary",
        "sup",
        "svg",
        "table",
        "tbody",
        "td",
        "template",
        "textarea",
        "tfoot",
        "th",
        "thead",
        "time",
        "title",
        "tr",
        "track",
        "tt",
        "u",
        "ul",
        "var",
        "video",
        "wbr",
    }.ToFrozenSet(StringComparer.Ordinal);
 
    internal static readonly ImmutableArray<string> FormEvents =
    [
        "onabort",
        "onblur",
        "onchange",
        "onclick",
        "oncontextmenu",
        "ondblclick",
        "onerror",
        "onfocus",
        "oninput",
        "onkeydown",
        "onkeypress",
        "onkeyup",
        "onload",
        "onmousedown",
        "onmousemove",
        "onmouseout",
        "onmouseover",
        "onmouseup",
        "onreset",
        "onscroll",
        "onselect",
        "onsubmit",
    ];
 
    public static bool IsHtmlTagName(string name)
        => s_htmlSchemaTagNames.Contains(name);
 
    public static bool TryGetElementInfo(
        SyntaxNode element,
        out SyntaxToken containingTagNameToken,
        out SyntaxList<RazorSyntaxNode> attributeNodes,
        out SyntaxToken closingForwardSlashOrCloseAngleToken)
    {
        switch (element)
        {
            case BaseMarkupStartTagSyntax startTag:
                {
                    containingTagNameToken = startTag.Name;
                    attributeNodes = startTag.Attributes;
 
                    closingForwardSlashOrCloseAngleToken = startTag.ForwardSlash.IsValid(out var forwardSlash)
                        ? forwardSlash
                        : startTag.CloseAngle;
                }
 
                return true;
 
            case BaseMarkupEndTagSyntax endTag:
                {
                    containingTagNameToken = endTag.Name;
                    attributeNodes = endTag.GetStartTag()?.Attributes ?? default;
 
                    closingForwardSlashOrCloseAngleToken = endTag.ForwardSlash.IsValid(out var forwardSlash)
                        ? forwardSlash
                        : endTag.CloseAngle;
                }
 
                return true;
 
            default:
                containingTagNameToken = default;
                attributeNodes = default;
                closingForwardSlashOrCloseAngleToken = default;
                return false;
        }
    }
 
    public static bool TryGetAttributeInfo(
        SyntaxNode attribute,
        out SyntaxToken containingTagNameToken,
        out TextSpan? prefixLocation,
        out string? selectedAttributeName,
        out TextSpan? selectedAttributeNameLocation,
        out SyntaxList<RazorSyntaxNode> attributeNodes)
    {
        if (!TryGetElementInfo(attribute.Parent, out containingTagNameToken, out attributeNodes, closingForwardSlashOrCloseAngleToken: out _))
        {
            containingTagNameToken = default;
            prefixLocation = null;
            selectedAttributeName = null;
            selectedAttributeNameLocation = null;
            attributeNodes = default;
            return false;
        }
 
        switch (attribute)
        {
            case MarkupMinimizedAttributeBlockSyntax minimizedAttributeBlock:
                prefixLocation = minimizedAttributeBlock.NamePrefix?.Span;
                selectedAttributeName = minimizedAttributeBlock.Name.GetContent();
                selectedAttributeNameLocation = minimizedAttributeBlock.Name.Span;
                return true;
            case MarkupAttributeBlockSyntax attributeBlock:
                prefixLocation = attributeBlock.NamePrefix?.Span;
                selectedAttributeName = attributeBlock.Name.GetContent();
                selectedAttributeNameLocation = attributeBlock.Name.Span;
                return true;
            case MarkupTagHelperAttributeSyntax tagHelperAttribute:
                prefixLocation = tagHelperAttribute.NamePrefix?.Span;
                selectedAttributeName = tagHelperAttribute.Name.GetContent();
                selectedAttributeNameLocation = tagHelperAttribute.Name.Span;
                return true;
            case MarkupMinimizedTagHelperAttributeSyntax minimizedAttribute:
                prefixLocation = minimizedAttribute.NamePrefix?.Span;
                selectedAttributeName = minimizedAttribute.Name.GetContent();
                selectedAttributeNameLocation = minimizedAttribute.Name.Span;
                return true;
            case MarkupTagHelperDirectiveAttributeSyntax tagHelperDirectiveAttribute:
                {
                    prefixLocation = tagHelperDirectiveAttribute.NamePrefix?.Span;
                    selectedAttributeName = tagHelperDirectiveAttribute.FullName;
                    var fullNameSpan = TextSpan.FromBounds(tagHelperDirectiveAttribute.Transition.Span.Start, tagHelperDirectiveAttribute.Name.Span.End);
                    selectedAttributeNameLocation = fullNameSpan;
                    return true;
                }
            case MarkupMinimizedTagHelperDirectiveAttributeSyntax minimizedTagHelperDirectiveAttribute:
                {
                    prefixLocation = minimizedTagHelperDirectiveAttribute.NamePrefix?.Span;
                    selectedAttributeName = minimizedTagHelperDirectiveAttribute.FullName;
                    var fullNameSpan = TextSpan.FromBounds(minimizedTagHelperDirectiveAttribute.Transition.Span.Start, minimizedTagHelperDirectiveAttribute.Name.Span.End);
                    selectedAttributeNameLocation = fullNameSpan;
                    return true;
                }
            case MarkupMiscAttributeContentSyntax:
                prefixLocation = null;
                selectedAttributeName = null;
                selectedAttributeNameLocation = null;
                return true;
        }
 
        // Not an attribute type that we know of
        prefixLocation = null;
        selectedAttributeName = null;
        selectedAttributeNameLocation = null;
        return false;
    }
}