public class XmlDocCommentReader()

in sdk/provisioning/Generator/src/Utilities/XmlDocCommentReader.cs [30:243]


public class XmlDocCommentReader(Assembly assembly)
{
    /// <summary>
    /// Gets the assembly explained by the doc comments.
    /// </summary>
    public Assembly Assembly { get; } = assembly;

    private readonly Lazy<IDictionary<string, XElement>> _members =
        new(() =>
        {
            string path = assembly.Location;
            if (string.IsNullOrEmpty(path))
            {
                throw new InvalidOperationException($"Could not load XML doc comments for assembly {assembly.FullName} because it has no Location.");
            }

            path = Path.ChangeExtension(path, ".xml");
            if (!File.Exists(path))
            {
                throw new InvalidOperationException($"XML doc comments for assembly {assembly.FullName} were not found at: {path}");
            }

            Dictionary<string, XElement> members = [];
            XDocument doc = XDocument.Load(path);
            foreach (XElement element in doc.Root!.Element("members")!.Elements("member"))
            {
                string name = element.Attribute("name")!.Value;
                members[name] = element;
            }
            return members;
        });

    /// <summary>
    /// Convert a member to its ID string as defined by
    /// <see href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments#d42-id-string-format">
    /// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments#d42-id-string-format</see>.
    /// </summary>
    /// <param name="member">The member.</param>
    /// <returns>The member's XML doc comment ID.</returns>
    /// <remarks>
    /// Some of the more esoteric features probably aren't supported correctly, but we're covered for
    /// the common patterns in Azure.ResourceManager.*
    /// </remarks>
    public static string AsXmlDocCommentId(MemberInfo member)
    {
        return member switch
        {
            Type t => $"T:{EncodeType(t)}",
            FieldInfo f => $"F:{EncodeType(f.DeclaringType!)}.{f.Name}",
            PropertyInfo p => $"P:{EncodeType(p.DeclaringType!)}.{p.Name}",
            MethodBase m => $"M:{EncodeType(m.DeclaringType!)}.{EncodeSignature(m)}",
            EventInfo e => $"E:{EncodeType(e.DeclaringType!)}.{e.Name}",
            _ => throw new InvalidOperationException($"Unexpected member: {member}")
        };

        static StringBuilder EncodeType(Type type, StringBuilder? text = default)
        {
            text ??= new();

            if (type.IsArray || type.IsPointer)
            {
                EncodeType(type.GetElementType()!, text);
                if (type.IsArray)
                {
                    text.Append('[');
                    for (int r = 0; r < type.GetArrayRank(); r++) { text.Append(','); }
                    text.Append(']');
                }
                else
                {
                    text.Append('*');
                }

                // Bail out early
                return text;
            }


            if (type.IsNested)
            {
                EncodeType(type.DeclaringType!, text).Append('.');
            }
            else
            {
                text.Append(type.Namespace).Append('.');
            }
            text.Append(type.Name);
            if (type.IsConstructedGenericType)
            {
                text.Append('{');
                IndentWriter.Fenceposter fence = new();
                foreach (Type arg in type.GenericTypeArguments)
                {
                    if (fence.RequiresSeparator) { text.Append(','); }
                    EncodeType(arg, text);
                }
                text.Append('}');
            }
            else if (type.IsGenericType)
            {
                text.Append('`').Append(type.GetGenericArguments().Length);
            }
            return text;
        }

        static StringBuilder EncodeSignature(MethodBase method, StringBuilder? text = default)
        {
            text ??= new();
            if (method.IsConstructor)
            {
                text.Append('#');
                if (method.IsStatic) { text.Append('c'); }
                text.Append("ctor");
            }
            else
            {
                text.Append(method.Name);
            }

            ParameterInfo[] parameters = method.GetParameters();
            if (parameters.Length > 0)
            {
                IndentWriter.Fenceposter fence = new();
                text.Append('(');
                foreach (ParameterInfo param in parameters)
                {
                    if (fence.RequiresSeparator) { text.Append(','); }
                    if (param.ParameterType.IsGenericMethodParameter)
                    {
                        text.Append("``");
                        text.Append(Array.IndexOf(method.GetGenericArguments(), param.ParameterType));
                    }
                    else if (param.ParameterType.IsGenericParameter)
                    {
                        text.Append('`');
                        text.Append(Array.IndexOf(method.DeclaringType!.GetGenericArguments(), param.ParameterType));
                    }
                    else
                    {
                        EncodeType(param.ParameterType, text);
                    }
                    if (param.IsIn || param.IsOut) { text.Append('@'); }
                }
                text.Append(')');
            }

            if (method.Name == "op_Explicit" || method.Name == "op_Implicit")
            {
                text.Append('~');
                EncodeType(((MethodInfo)method).ReturnType, text);
            }

            return text;
        }
    }

    private static string? Clean(string? text)
    {
        if (string.IsNullOrEmpty(text)) { return text; }
        return text.Replace('\n', ' ').Replace('\r', ' ').Trim();
    }

    public string? GetSummary(MemberInfo member) =>
        _members.Value.TryGetValue(AsXmlDocCommentId(member), out XElement? e) ?
            Clean(GetInnerText(e.Element("summary")).ToString()) :
            null;

    public string? GetSummary(ParameterInfo param)
    {
        if (_members.Value.TryGetValue(AsXmlDocCommentId(param.Member), out XElement? method))
        {
            foreach (var e in method.Elements("param"))
            {
                if (e.Attribute("name")?.Value == param.Name)
                {
                    return Clean(GetInnerText(e).ToString());
                }
            }
        }
        return null;
    }

    private static StringBuilder GetInnerText(XElement? element, StringBuilder? text = default)
    {
        text ??= new();
        if (element is not null)
        {
            foreach (XNode node in element.Nodes())
            {
                if (node is XElement e)
                {
                    GetInnerText(e, text);
                }
                else if (node is XText t)
                {
                    text.Append(t.Value);
                }
            }

            if (element.Name.LocalName == "see")
            {
                if (element.Attribute("cref") is { Value: string id })
                {
                    text.Append(id[2..]); // Stripping off the prefix makes it kind of legible
                }
                else if (element.Attribute("href") is { Value: string href })
                {
                    text.Append(href);
                }
            }
        }
        return text;
    }
}