< Summary

Information
Class: AmbientServices.ExcludeInDocumentationAttribute
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Services/DotNetDocumentation.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 2
Uncovered lines: 0
Coverable lines: 2
Total lines: 945
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/Services/DotNetDocumentation.cs

#LineLine coverage
 1using AmbientServices.Extensions;
 2using System;
 3using System.Collections.Generic;
 4using System.Linq;
 5using System.Reflection;
 6using System.Threading;
 7using System.Threading.Tasks;
 8using System.Xml;
 9using System.Xml.XPath;
 10
 11namespace AmbientServices;
 12
 13/// <summary>
 14/// An enumeration of the types of members in .NET xml documentation files, with their values as the characters in the n
 15/// </summary>
 16public static class DocumentationMemberType
 17{
 18    /// <summary>
 19    /// Indicates that the documentation is for a type (class, struct, or delegate).
 20    /// </summary>
 21    public const char Type = 'T';
 22    /// <summary>
 23    /// Indicates that the documentation is for a method.
 24    /// </summary>
 25    public const char Method = 'M';
 26    /// <summary>
 27    /// Indicates that the documentation is for a field or enum member.
 28    /// </summary>
 29    public const char Field = 'F';
 30    /// <summary>
 31    /// Indicates that the documentation is for a property.
 32    /// </summary>
 33    public const char Property = 'P';
 34    /// <summary>
 35    /// Indicates that the documentation is for an event.
 36    /// </summary>
 37    public const char Event = 'E';
 38}
 39
 40/// <summary>
 41/// A class that manages access to a .NET XML documentation file.
 42/// </summary>
 43/// <remarks>
 44/// <pitch>Runtime access to the XML documentation the compiler wrote for an assembly: hand it a <see cref="Type"/>, met
 45/// <pledge>
 46/// <see cref="Load(Assembly)"/> never fails for a missing or absent documentation file — it returns an object whose que
 47/// The Get*Documentation methods return null (rather than throwing) whenever the member has no documentation entry, let
 48/// The <see cref="ProxyTypeAttribute"/>, <see cref="IncludeTypeInDocumentationAttribute"/>, and <see cref="ExcludeInDoc
 49/// </pledge>
 50/// <plan>
 51/// Locates the documentation file by convention beside the assembly (same base name, <c>.xml</c> extension), parses it 
 52/// The whole document stays in memory for the life of the process — a trade of memory for query speed appropriate to it
 53/// </plan>
 54/// </remarks>
 55public class DotNetDocumentation
 56{
 57    private static readonly Dictionary<string, DotNetDocumentation> sDocumentations = new();
 58
 59    private readonly XPathNavigator _documentRoot;
 60    private readonly List<Type> _types;
 61
 62    /// <summary>
 63    /// Loads the documentation for the specified assembly.
 64    /// </summary>
 65    /// <param name="type">The <see cref="Type"/> to load documentation for.</param>
 66    /// <returns>A <see cref="DotNetDocumentation"/> object that can be used to access the documentation for the specifi
 67    public static DotNetDocumentation Load(Type type)
 68    {
 69        if (type == null) throw new ArgumentNullException(nameof(type));
 70        return Load(type.Assembly);
 71    }
 72    /// <summary>
 73    /// Loads the documentation for the specified assembly.
 74    /// </summary>
 75    /// <param name="assembly">The assembly to load documentation for.</param>
 76    /// <returns>A <see cref="DotNetDocumentation"/> object that can be used to access the documentation for the specifi
 77    public static DotNetDocumentation Load(Assembly assembly)
 78    {
 79        if (assembly == null) throw new ArgumentNullException(nameof(assembly));
 80        // get the fileName
 81        string documentationFileName = DocumentationFile(assembly) ?? string.Empty;
 82        // look it up in the cache to see if we've already loaded it
 83        lock (sDocumentations)
 84        {
 85            // already loaded?
 86            if (sDocumentations.ContainsKey(documentationFileName.ToUpperInvariant()))
 87            {
 88                // return the cached one
 89                return sDocumentations[documentationFileName.ToUpperInvariant()];
 90            }
 91        }
 92        Type[] types = assembly.GetLoadableTypes();
 93        // load the documentation, if it exists, otherwise use empty documentation
 94        DotNetDocumentation documentation = (!string.IsNullOrEmpty(documentationFileName) && System.IO.File.Exists(docum
 95        lock (sDocumentations)
 96        {
 97            // put it in (yes, someone else may have already done so, but no big deal)!
 98            sDocumentations[documentationFileName.ToUpperInvariant()] = documentation;
 99        }
 100        return documentation;
 101    }
 102
 103    private DotNetDocumentation(string xmlDocumentationFilePath, IEnumerable<Type> types)
 104    {
 105        // open the documentation file
 106        using System.IO.Stream stream = new System.IO.FileStream(xmlDocumentationFilePath, System.IO.FileMode.Open, Syst
 107        using XmlReader reader = XmlReader.Create(stream);
 108        // NOTE: as of 2021-05-13, there appears to be a bug in VS that causes a failure here because the XML documentat
 109        _documentRoot = new XPathDocument(reader).CreateNavigator();
 110        _types = new(types);
 111    }
 112    private DotNetDocumentation()
 113    {
 114        XmlDocument doc = new();
 115        _documentRoot = doc.CreateNavigator()!; // we just created the XmlDocument, so it should be empty and should beh
 116        _types = new();
 117    }
 118    /// <summary>
 119    /// Gets an enumeration of the public <see cref="Type"/>s in the corresponding assembly, which should be documented.
 120    /// </summary>
 121    public IEnumerable<Type> PublicTypes => _types;
 122    internal static string BuildDisambiguatingParameterList(MethodBase method)
 123    {
 124        ParameterInfo[] parameters = method.GetParameters();
 125        // when there are NO parameters, we output empty string (not "()")
 126        if (parameters == null || parameters.Length == 0)
 127        {
 128            return string.Empty;
 129        }
 130        System.Text.StringBuilder ret = new();
 131        ret.Append('(');
 132        // loop through all the parameters
 133        for (int i = 0; i < parameters.Length; i++)
 134        {
 135            if (i > 0) ret.Append(',');
 136            var parameter = parameters[i];
 137            if (parameter.ParameterType.IsGenericParameter)
 138            {
 139                // ECMA XML doc IDs use `0, `1, ... for method and type generic parameters (not the metadata name "T").
 140                Type[]? genericArgs = null;
 141                if (method is MethodInfo mi && mi.IsGenericMethod)
 142                {
 143                    genericArgs = mi.GetGenericMethodDefinition().GetGenericArguments();
 144                }
 145                else if (method.DeclaringType?.IsGenericTypeDefinition == true)
 146                {
 147                    genericArgs = method.DeclaringType.GetGenericArguments();
 148                }
 149                int gpIndex = genericArgs == null ? -1 : Array.IndexOf(genericArgs, parameter.ParameterType);
 150                if (gpIndex >= 0)
 151                {
 152                    ret.Append("``");
 153                    ret.Append(gpIndex.ToString(System.Globalization.CultureInfo.InvariantCulture));
 154                }
 155                else
 156                {
 157                    ret.Append(parameter.ParameterType.Name);
 158                }
 159            }
 160            else if (parameter.ParameterType.IsGenericType)
 161            { // put in the generic type name as listed in XML documentation parameter lists, which is GenericType{TypeP
 162                string genericTypeName = parameter.ParameterType.GetGenericTypeDefinition().FullName ?? "UnknownType";
 163                int backtickIndex = genericTypeName.IndexOf('`'
 164#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET5_0_OR_GREATER
 165                    , StringComparison.Ordinal
 166#endif
 167                    );
 168                if (backtickIndex >= 0)
 169                {
 170                    genericTypeName = genericTypeName.Substring(0, backtickIndex);
 171                }
 172                ret.Append(genericTypeName);
 173                ret.Append('{');
 174                ret.Append(string.Join(",", parameter.ParameterType.GetGenericArguments().Select(t => t.FullName)));
 175                ret.Append('}');
 176            }
 177            else
 178            {
 179                ret.Append(parameter.ParameterType.FullName);
 180            }
 181        }
 182        // replace & with @
 183        ret.Replace('&', '@');
 184        ret.Append(')');
 185        // return the string we built
 186        return ret.ToString();
 187    }
 188    private XPathNavigator? TypeDocumentation(Type type)
 189    {
 190        if (type.FullName == null) return null;
 191        string nodePath = $"/doc/members/member[@name=\"{DocumentationMemberType.Type}:{type.FullName.Replace('+', '.')}
 192        return _documentRoot.SelectSingleNode(nodePath);
 193    }
 194    private XPathNavigator? MethodDocumentation(MethodBase method)
 195    {
 196        if (method.DeclaringType?.FullName == null) return null;
 197        string methodName = method.Name;
 198        if (methodName == ".ctor") methodName = "#ctor";
 199        if (method is MethodInfo methodInfo)
 200        {
 201            MethodInfo docIdentity = methodInfo.IsGenericMethod ? methodInfo.GetGenericMethodDefinition() : methodInfo;
 202            if (docIdentity.IsGenericMethodDefinition)
 203            {
 204                // ECMA IDs use two backticks before generic arity (e.g. ValueTaskFromResult``1).
 205                methodName += "``" + docIdentity.GetGenericArguments().Length.ToString(System.Globalization.CultureInfo.
 206            }
 207        }
 208        string nodePath = $"/doc/members/member[@name=\"{DocumentationMemberType.Method}:{method.DeclaringType.FullName.
 209        return _documentRoot.SelectSingleNode(nodePath);
 210    }
 211    private XPathNavigator? PropertyDocumentation(PropertyInfo property)
 212    {
 213        if (property.DeclaringType?.FullName == null) return null;
 214        string nodePath = $"/doc/members/member[@name=\"{DocumentationMemberType.Property}:{property.DeclaringType.FullN
 215        return _documentRoot.SelectSingleNode(nodePath);
 216    }
 217    private XPathNavigator? FieldDocumentation(FieldInfo field)
 218    {
 219        if (field.DeclaringType?.FullName == null) return null;
 220        string nodePath = $"/doc/members/member[@name=\"{DocumentationMemberType.Field}:{field.DeclaringType.FullName.Re
 221        return _documentRoot.SelectSingleNode(nodePath);
 222    }
 223    private static string? DocumentationFile(Assembly assembly)
 224    {
 225        if (assembly.IsDynamic) return null;
 226        string documentationFileName = assembly.Location;
 227        documentationFileName = System.IO.Path.GetDirectoryName(documentationFileName) + System.IO.Path.DirectorySeparat
 228        return documentationFileName;
 229    }
 230
 231
 232    private static ParameterDocumentation[]? BuildTypeParameters(XPathNavigator nav)
 233    {
 234        List<ParameterDocumentation> parameters = new();
 235        if (nav != null)
 236        {
 237            XPathNodeIterator iterator = nav.SelectChildren("typeparam", string.Empty);
 238            while (iterator.MoveNext())
 239            {
 240                if (iterator.Current == null) continue;
 241                parameters.Add(new ParameterDocumentation(iterator.Current.GetAttribute("name", string.Empty), nav.Value
 242            }
 243        }
 244        // none found?
 245        if (parameters.Count < 1)
 246        {
 247            // use NULL instead of empty array so that the output xml looks nicer.
 248            return null;
 249        }
 250        return parameters.ToArray();
 251    }
 252
 253    private static ParameterDocumentation[]? BuildParameters(XPathNavigator nav)
 254    {
 255        List<ParameterDocumentation> parameters = new();
 256        if (nav != null)
 257        {
 258            XPathNodeIterator iterator = nav.SelectChildren("param", string.Empty);
 259            while (iterator.MoveNext())
 260            {
 261                if (iterator.Current == null) continue;
 262                parameters.Add(new ParameterDocumentation(iterator.Current.GetAttribute("name", string.Empty), GetNodeCo
 263            }
 264        }
 265        // none found?
 266        if (parameters.Count < 1)
 267        {
 268            // use NULL instead of empty array so that the output xml looks nicer.
 269            return null;
 270        }
 271        return parameters.ToArray();
 272    }
 273
 274    private static string? GetNodeContents(XPathNavigator nav, string element)
 275    {
 276        XPathNavigator? node = nav?.SelectSingleNode(element);
 277        if (node != null)
 278        {
 279            return GetNodeContents(node);
 280        }
 281        return null;
 282    }
 283    private static string? GetNodeContents(XPathNavigator nav)
 284    {
 285        if (nav == null) return null;
 286        string contents = nav.InnerXml.Trim();
 287        return (contents.Trim().Length < 1) ? null : contents;
 288    }
 289    //private static string GetPlainTextNodeContents(XPathNavigator nav)
 290    //{
 291    //    string contents = (nav == null) ? string.Empty : nav.Value.Trim();
 292    //    contents = string.IsNullOrEmpty(contents) ? null : contents.Trim();
 293    //    return contents;
 294    //}
 295
 296    private static readonly HashSet<Type> _StandardTypes = new(new Type[] {
 297        typeof(void), typeof(Task), typeof(ValueTask),
 298        typeof(string), typeof(char), typeof(bool), typeof(byte), typeof(sbyte),
 299        typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong),
 300        typeof(float), typeof(double),
 301        typeof(Guid), typeof(DateTime), typeof(DateTimeOffset), typeof(TimeSpan), typeof(Uri),
 302#if NET6_0_OR_GREATER
 303        typeof(DateOnly), typeof(TimeOnly),
 304#endif
 305        typeof(WaitHandle), typeof(CancellationToken), typeof(Microsoft.Win32.SafeHandles.SafeWaitHandle)
 306    });
 307    /// <summary>
 308    /// Gets an enumeration of standard types (that don't need documenting).
 309    /// </summary>
 310    public static IEnumerable<Type> StandardTypes => _StandardTypes;
 311    /// <summary>
 312    /// Gets the type of the specified type, or a proxy type if the type has a JSON proxy type.
 313    /// </summary>
 314    /// <param name="type">The actual runtime type.</param>
 315    /// <returns>The <see cref="Type"/> that was passed in, or a proxy <see cref="Type"/> if there is a custom serialize
 316    public static Type GetTypeOrProxy(Type type)
 317    {
 318        return type.GetCustomAttribute<ProxyTypeAttribute>()?.Type ?? type;
 319    }
 320    /// <summary>
 321    /// Gets the documentation for the specified <see cref="System.Type"/>.
 322    /// </summary>
 323    /// <param name="type">The <see cref="System.Type"/> to get documentation for.</param>
 324    /// <returns>A <see cref="TypeDocumentation"/> containing documentation for the type, if one could be found.</return
 325    public TypeDocumentation? GetTypeDocumentation(Type type)
 326    {
 327#if NET5_0_OR_GREATER
 328        ArgumentNullException.ThrowIfNull(type);
 329#else
 330        if (type == null) throw new ArgumentNullException(nameof(type));
 331#endif
 332        XPathNavigator? nav = TypeDocumentation(type);
 333        if (type.FullName == null || nav == null) return null;
 334        return new TypeDocumentation(GetHumanReadableTypeName(type), GetNodeContents(nav, "summary"), GetNodeContents(na
 335    }
 336
 337    internal static string GetHumanReadableTypeName(Type type)
 338    {
 339        if (type.IsArray)
 340        {
 341            return $"{GetHumanReadableTypeName(type.GetElementType()!)}[]"; // GetElementType() should never return null
 342        }
 343        if (type.IsGenericType)
 344        {
 345            if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
 346            {
 347                return $"{GetHumanReadableTypeName(type.GetGenericArguments()[0])}?";
 348            }
 349            string genericTypeName = type.GetGenericTypeDefinition().Name;
 350            int backtickIndex = genericTypeName.IndexOf('`'
 351#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET5_0_OR_GREATER
 352                , StringComparison.Ordinal
 353#endif
 354                );
 355            if (backtickIndex >= 0)
 356            {
 357                genericTypeName = genericTypeName.Substring(0, backtickIndex);
 358            }
 359            var genericArgs = string.Join(", ", type.GetGenericArguments().Select(GetHumanReadableTypeName));
 360            return $"{genericTypeName}<{genericArgs}>";
 361        }
 362        if (type.IsGenericParameter)
 363        {
 364            return type.Name;
 365        }
 366        if (type.DeclaringType != null && type.Name.Contains('<'
 367#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER || NET5_0_OR_GREATER
 368                , StringComparison.Ordinal
 369#endif
 370            ))
 371        {
 372            // Handle compiler-generated types (e.g., async state machines)
 373            var declaringTypeName = GetHumanReadableTypeName(type.DeclaringType);
 374            var simpleName = type.Name.Split('<')[0];
 375            return $"{declaringTypeName}.{simpleName}";
 376        }
 377        return type.Name;
 378    }
 379
 380    /// <summary>
 381    /// Gets the documentation for a <see cref="Nullable{T}"/> of the specified <see cref="System.Type"/>.
 382    /// </summary>
 383    /// <param name="type">The <see cref="System.Type"/> to get documentation for.</param>
 384    /// <returns>A <see cref="TypeDocumentation"/> containing documentation for the type, if one could be found.</return
 385    public TypeDocumentation? GetNullableTypeDocumentation(Type type)
 386    {
 387#if NET5_0_OR_GREATER
 388        ArgumentNullException.ThrowIfNull(type);
 389#else
 390        if (type == null) throw new ArgumentNullException(nameof(type));
 391#endif
 392        XPathNavigator? nav = TypeDocumentation(type);
 393        if (type.FullName == null || nav == null) return null;
 394        return new TypeDocumentation("Nullable<" + GetHumanReadableTypeName(type) + ">", GetNodeContents(nav, "summary")
 395    }
 396    /// <summary>
 397    /// Gets documentation for the specified method.
 398    /// </summary>
 399    /// <param name="method">A <see cref="MethodInfo"/> identifying the method to get documentation for.</param>
 400    /// <returns>A <see cref="MethodDocumentation"/> containing documentation for the specified method, if one could be 
 401    public MethodDocumentation? GetMethodDocumentation(MethodBase method)
 402    {
 403#if NET5_0_OR_GREATER
 404        ArgumentNullException.ThrowIfNull(method);
 405#else
 406        if (method == null) throw new ArgumentNullException(nameof(method));
 407#endif
 408        if (method.DeclaringType == null) return null;
 409        XPathNavigator? nav = MethodDocumentation(method);
 410        if (method.Name == null || nav == null) return null;
 411        return new MethodDocumentation(method.Name, BuildParameters(nav), GetNodeContents(nav, "summary"), GetNodeConten
 412    }
 413    /// <summary>
 414    /// Gets documentation for the specified property.
 415    /// </summary>
 416    /// <param name="property">A <see cref="PropertyInfo"/> identifying the property to get documentation for.</param>
 417    /// <returns>A <see cref="MemberDocumentation"/> containing documentation for the specified property, if one could b
 418    public MemberDocumentation? GetPropertyDocumentation(PropertyInfo property)
 419    {
 420#if NET5_0_OR_GREATER
 421        ArgumentNullException.ThrowIfNull(property);
 422#else
 423        if (property == null) throw new ArgumentNullException(nameof(property));
 424#endif
 425        if (property.DeclaringType == null) return null;
 426        XPathNavigator? nav = PropertyDocumentation(property);
 427        if (property.Name == null || nav == null) return null;
 428        return new MemberDocumentation(property.Name, GetNodeContents(nav, "summary"), GetNodeContents(nav, "remarks"));
 429    }
 430    /// <summary>
 431    /// Gets documentation for the specified field.
 432    /// </summary>
 433    /// <param name="field">A <see cref="FieldInfo"/> identifying the field to get documentation for.</param>
 434    /// <returns>A <see cref="MemberDocumentation"/> containing documentation for the specified field, if one could be f
 435    public MemberDocumentation? GetFieldDocumentation(FieldInfo field)
 436    {
 437#if NET5_0_OR_GREATER
 438        ArgumentNullException.ThrowIfNull(field);
 439#else
 440        if (field == null) throw new ArgumentNullException(nameof(field));
 441#endif
 442        if (field.DeclaringType == null) return null;
 443        XPathNavigator? nav = FieldDocumentation(field);
 444        if (field.Name == null || nav == null) return null;
 445        return new MemberDocumentation(field.Name, GetNodeContents(nav, "summary"), GetNodeContents(nav, "remarks"));
 446    }
 447    /// <summary>
 448    /// Gets documentation for the specified member.
 449    /// </summary>
 450    /// <param name="member">A <see cref="MemberInfo"/> identifying the member to get documentation for.</param>
 451    /// <returns>A <see cref="MemberDocumentation"/> containing documentation for the specified member, if one could be 
 452    public MemberDocumentation? GetMemberDocumentation(MemberInfo member)
 453    {
 454        PropertyInfo? memberProperty = member as PropertyInfo;
 455        FieldInfo? memberField = member as FieldInfo;
 456        if (memberProperty != null)
 457        {
 458            return GetPropertyDocumentation(memberProperty);
 459        }
 460        else if (memberField != null)
 461        {
 462            return GetFieldDocumentation(memberField);
 463        }
 464        return null;
 465    }
 466#if LATER
 467    /// <summary>
 468    /// Reconstructs documentation for a method previously retrieved.
 469    /// </summary>
 470    /// <param name="linePrefix">A prefix for the line, including spaces to tab in the comments and the comment characte
 471    /// <param name="method">The <see cref="MethodInfo"/> for the method to get documentation for.</param>
 472    /// <returns>Reconstructed XML documentation for the specified method.</returns>
 473    public static string ReconstructMethodDocumentation(string linePrefix, MethodInfo method)
 474    {
 475        StringBuilder output = new StringBuilder();
 476        MethodDocumentation docs = NetDocumentation.GetMethodDocumentation(method);
 477        if (docs.Summary != null)
 478        {
 479            output.Append(ReconstructDocumentation(linePrefix, "summary", docs.Summary));
 480        }
 481        // a composite, so get a list of members (fields or properties)
 482        foreach (ParameterDocumentation parameter in docs.Parameters)
 483        {
 484            bool multiline = (linePrefix.Length + parameter.Description.Length + 23) > 80;
 485            output.Append(linePrefix);
 486            output.Append("<param name=\"");
 487            output.Append(parameter.Name);
 488            output.Append("\">");
 489            if (multiline)
 490            {
 491                output.Append("\r\n");
 492            }
 493            foreach (string line in NormalizeComment(parameter.Description))
 494            {
 495                output.Append(linePrefix);
 496                output.Append(line);
 497                if (multiline)
 498                {
 499                    output.Append("\r\n");
 500                }
 501            }
 502            output.Append("</param>");
 503        }
 504        if (!string.IsNullOrEmpty(docs.Remarks))
 505        {
 506            output.Append(ReconstructDocumentation(linePrefix, "remarks", docs.Remarks));
 507        }
 508        if (!string.IsNullOrEmpty(docs.ReturnDescription))
 509        {
 510            output.Append(ReconstructDocumentation(linePrefix, "returns", docs.ReturnDescription));
 511        }
 512        return output.ToString();
 513    }
 514    /// <summary>
 515    /// Reconstructs documentation for a type previously retrieved.
 516    /// </summary>
 517    /// <param name="linePrefix">A prefix for the comment lines, including spaces to tab in the comments and the comment
 518    /// <param name="type">The <see cref="Type"/> being documented.</param>
 519    /// <param name="hasMembers">Whether or not the type has members (some types do not).</param>
 520    /// <returns>Reconstructed XML documentation for the specified type.</returns>
 521    public static string ReconstructTypeDocumentation(string linePrefix, Type type, bool hasMembers)
 522    {
 523        // create a place to build the output string efficiently
 524        StringBuilder output = new StringBuilder();
 525        // get the documentation for the specified type
 526        TypeDocumentation docs = NetDocumentation.GetTypeDocumentation(type);
 527        // write out the summary (if needed)
 528        if (docs.Summary != null)
 529        {
 530            output.Append(ReconstructDocumentation(linePrefix, "summary", docs.Summary));
 531        }
 532        // write out the remarks (if needed)
 533        if (!string.IsNullOrEmpty(docs.Remarks))
 534        {
 535            output.Append(ReconstructDocumentation(linePrefix, "remarks", docs.Remarks));
 536        }
 537        // are the members to write as well?
 538        if (hasMembers)
 539        {
 540            // loop through the members
 541            foreach (MemberInfo member in ModelUtility.EnumerateFieldsAndProperties(type))
 542            {
 543                // call out to the other function to construct those and add them here
 544                output.Append(ReconstructMemberDocumentation(linePrefix, member));
 545            }
 546        }
 547        // return the string we built
 548        return output.ToString();
 549    }
 550
 551    private static string ReconstructMemberDocumentation(string linePrefix, MemberInfo member)
 552    {
 553        StringBuilder output = new StringBuilder();
 554        MemberDocumentation docs = GetMemberDocumentation(member);
 555        if (docs.Summary != null)
 556        {
 557            output.Append(ReconstructDocumentation(linePrefix, "summary", docs.Summary));
 558        }
 559        if (!string.IsNullOrEmpty(docs.Remarks))
 560        {
 561            output.Append(ReconstructDocumentation(linePrefix, "remarks", docs.Remarks));
 562        }
 563        return output.ToString();
 564    }
 565
 566    /// <summary>
 567    /// Reconstructs documentation parts.
 568    /// </summary>
 569    /// <param name="linePrefix">A prefix for the comment lines, including spaces to tab in the comments and the comment
 570    /// <param name="blockType">The block type (the xml element name).</param>
 571    /// <param name="contents">The contents (text) to put inside the xml.</param>
 572    /// <returns>Reconstructed XML documentation.</returns>
 573    public static string ReconstructDocumentation(string linePrefix, string blockType, string contents)
 574    {
 575        // do single line?
 576        bool singleLine = (linePrefix.Length + contents.Length + ((blockType == null) ? 0 : (blockType.Length + 5)) < 80
 577        // build the output
 578        StringBuilder output = new StringBuilder();
 579        if (blockType != null)
 580        {
 581            output.Append("\r\n");
 582            output.Append(linePrefix);
 583            output.Append("<");
 584            output.Append(blockType);
 585            output.Append(">");
 586        }
 587        foreach (string commentLine in NormalizeComment(contents))
 588        {
 589            if (!singleLine)
 590            {
 591                output.Append("\r\n");
 592                output.Append(linePrefix);
 593            }
 594            output.Append(commentLine);
 595        }
 596        if (blockType != null)
 597        {
 598            if (!singleLine)
 599            {
 600                output.Append("\r\n");
 601                output.Append(linePrefix);
 602            }
 603            output.Append("</");
 604            output.Append(blockType);
 605            output.Append(">");
 606        }
 607        return output.ToString();
 608    }
 609    /// <summary>
 610    /// Unwraps the specified documentation comment, stripping line breaks and redundant whitespace.
 611    /// </summary>
 612    /// <param name="comment">The multi-line comment.</param>
 613    /// <returns>A single-line string.</returns>
 614    public static string UnwrapComment(string comment)
 615    {
 616        StringBuilder unwrapped = new StringBuilder();
 617        foreach (string line in NormalizeComment(comment))
 618        {
 619            unwrapped.Append(line);
 620        }
 621        return unwrapped.ToString();
 622    }
 623    /// <summary>
 624    /// Normalizes the specified comment string by combining lines and removing redundant spaces.
 625    /// </summary>
 626    /// <param name="comment">The multi-line comment.</param>
 627    /// <returns>The normalized version of the comment.</returns>
 628    public static IEnumerable<string> NormalizeComment(string comment)
 629    {
 630        if (comment != null)
 631        {
 632            List<string> lines = new List<string>();
 633            // read in the lines one at a time, keeping track of the minimal number of leading spaces
 634            System.IO.StringReader reader = new System.IO.StringReader(comment);
 635            int leadingSpaces = int.MaxValue;
 636            int lastNonBlankLine = -1;
 637            int lineNumber;
 638            for (lineNumber = 0; reader.Peek() != -1; ++lineNumber)
 639            {
 640                // read this line
 641                string line = reader.ReadLine();
 642                // not a blank line?
 643                if (line.Trim().Length > 0)
 644                {
 645                    lastNonBlankLine = lineNumber;
 646                }
 647                // add it to the output
 648                lines.Add(line);
 649                // keep track of the least number of leading spaces
 650                int lineLeadingSpaces = line.Length - line.TrimStart(' ').Length;
 651                if (lineLeadingSpaces < leadingSpaces)
 652                {
 653                    leadingSpaces = lineLeadingSpaces;
 654                }
 655            }
 656            lineNumber = 0;
 657            for (lineNumber = 0; lineNumber <= lastNonBlankLine; ++lineNumber)
 658            {
 659                // trim the incoming line to remove redundant leading spaces
 660                string trimmed = lines[lineNumber].Substring(leadingSpaces);
 661                // normalize this one comment line and output it
 662                yield return NormalizeCommentLine(trimmed);
 663            }
 664        }
 665    }
 666
 667    private static string NormalizeCommentLine(string trimmed)
 668    {
 669        StringBuilder outputLine = new StringBuilder();
 670        int cursor = 0;
 671        while (true)
 672        {
 673            int nextStop = trimmed.IndexOf("cref=\"", cursor, StringComparison.OrdinalIgnoreCase);
 674            // no more?
 675            if (nextStop < 0)
 676            {
 677                // output everything from the cursor to the end
 678                outputLine.Append(trimmed.Substring(cursor));
 679                // we're done!
 680                break;
 681            }
 682            // move to the contents of the cref
 683            nextStop += 6;
 684            // output everything up to that point
 685            outputLine.Append(trimmed.Substring(cursor, nextStop - cursor));
 686            // remove the typing and qualification from the item name
 687            int endOffset = trimmed.IndexOf("\"", nextStop, StringComparison.Ordinal);
 688            if (endOffset > 0)
 689            {
 690                string contents = trimmed.Substring(nextStop, endOffset - nextStop);
 691                string[] contentsSplit = contents.Split(':', '(');
 692                string[] reference = contentsSplit[(contentsSplit.Length > 1) ? 1 : 0].Split('.');
 693                // is the reference qualified?
 694                if (reference.Length > 1)
 695                {
 696                    // ignore qualification for now
 697                }
 698                // spit out the unqualified reference
 699                outputLine.Append(reference[reference.Length - 1]);
 700            }
 701            // move the cursor
 702            cursor = endOffset;
 703        }
 704        return outputLine.ToString();
 705    }
 706#endif
 707}
 708#if NET5_0_OR_GREATER
 709/// <summary>
 710/// An immutable class that contains the documentation for a <see cref="Type"/>.
 711/// </summary>
 712/// <param name="Name">The name of the type.</param>
 713/// <param name="Summary">The summary specified in the xml documentation comments.</param>
 714/// <param name="Remarks">The remarks specified in the xml documentation comments.</param>
 715/// <param name="TypeParameters">An enumeration of documentation information for the parameters to the <see cref="Type"/
 716public record TypeDocumentation(string Name, string? Summary, string? Remarks, IEnumerable<ParameterDocumentation>? Type
 717
 718/// <summary>
 719/// An immutable class that contains the documentation for a parameter.
 720/// </summary>
 721/// <param name="Name">The name of the parameter.</param>
 722/// <param name="Description">The description of the parameter as specified in the xml documentation comments, if any.</
 723public record ParameterDocumentation(string Name, string? Description);
 724
 725/// <summary>
 726/// A record that contains documentation for a method.
 727/// </summary>
 728/// <param name="Name">The name of the method.</param>
 729/// <param name="Parameters">An enumeration of documentation for the parameters to the method.</param>
 730/// <param name="Summary">The summary specified in the xml documentation comments.</param>
 731/// <param name="Remarks">The remarks specified in the xml documentation comments.</param>
 732/// <param name="ReturnDescription">The description of the return value specified in the xml documentation comments.</pa
 733/// <param name="TypeParameters">An enumeration of documentation information for type parameters to the method if it is 
 734public record MethodDocumentation(string Name, IEnumerable<ParameterDocumentation>? Parameters, string? Summary, string?
 735
 736/// <summary>
 737/// A record that contains documentation for a member.
 738/// </summary>
 739/// <param name="Name">The name of the member.</param>
 740/// <param name="Summary">The summary specified in the xml documentation comments.</param>
 741/// <param name="Remarks">The remarks specified in the xml documentation comments.</param>
 742public record MemberDocumentation(string Name, string? Summary, string? Remarks);
 743
 744#else
 745
 746/// <summary>
 747/// An immutable class that contains the documentation for a <see cref="Type"/>.
 748/// </summary>
 749public class TypeDocumentation
 750{
 751    /// <summary>
 752    /// The name of the type.
 753    /// </summary>
 754    public string Name { get; private set; }
 755    /// <summary>
 756    /// The summary specified in the xml documentation comments.
 757    /// </summary>
 758    public string? Summary { get; private set; }
 759    /// <summary>
 760    /// The remarks specified in the xml documentation comments.
 761    /// </summary>
 762    public string? Remarks { get; private set; }
 763    /// <summary>
 764    /// An enumeration of documentation information for the parameters to the <see cref="Type"/> if it is a generic type
 765    /// </summary>
 766    public IEnumerable<ParameterDocumentation>? TypeParameters { get; private set; }
 767
 768    /// <summary>
 769    /// Constructs a TypeDocumentation object.
 770    /// </summary>
 771    /// <param name="name">The name of the type.</param>
 772    /// <param name="summary">The summary for the type.  Optional.</param>
 773    /// <param name="remarks">The remarks for the type.  Optional.</param>
 774    /// <param name="typeParameters">The type parameters for the type.  Optional.</param>
 775    public TypeDocumentation(string name, string? summary = null, string? remarks = null, IEnumerable<ParameterDocumenta
 776    {
 777        Name = name;
 778        Summary = summary;
 779        Remarks = remarks;
 780        TypeParameters = typeParameters;
 781    }
 782}
 783
 784/// <summary>
 785/// An immutable class that contains the documentation for a parameter.
 786/// </summary>
 787public class ParameterDocumentation
 788{
 789    /// <summary>
 790    /// The name of the parameter.
 791    /// </summary>
 792    public string Name { get; private set; }
 793    /// <summary>
 794    /// The description of the parameter as specified in the xml documentation comments, if any.
 795    /// </summary>
 796    public string? Description { get; private set; }
 797
 798    /// <summary>
 799    /// Constructs a ParameterDocumentation with the specified name and description.
 800    /// </summary>
 801    /// <param name="name">The name of the parameter.</param>
 802    /// <param name="description">The description of the parameter.</param>
 803    public ParameterDocumentation(string name, string? description)
 804    {
 805        Name = name;
 806        Description = description;
 807    }
 808}
 809
 810/// <summary>
 811/// An immutable class that contains documentation for a method.
 812/// </summary>
 813public class MethodDocumentation
 814{
 815    /// <summary>
 816    /// The name of the method.
 817    /// </summary>
 818    public string Name { get; private set; }
 819    /// <summary>
 820    /// An enumeration of documentation for the parameters to the method.
 821    /// </summary>
 822    public IEnumerable<ParameterDocumentation>? Parameters { get; private set; }
 823    /// <summary>
 824    /// The summary specified in the xml documentation comments.
 825    /// </summary>
 826    public string? Summary { get; private set; }
 827    /// <summary>
 828    /// The remarks specified in the xml documentation comments.
 829    /// </summary>
 830    public string? Remarks { get; private set; }
 831    /// <summary>
 832    /// The description of the return value specified in the xml documentation comments.
 833    /// </summary>
 834    public string? ReturnDescription { get; private set; }
 835    /// <summary>
 836    /// An enumeration of documentation information for type parameters to the method if it is a generic method.
 837    /// </summary>
 838    public IEnumerable<ParameterDocumentation>? TypeParameters { get; private set; }
 839
 840    /// <summary>
 841    /// Constructs a MethodDocumentation with the specified parameters.
 842    /// </summary>
 843    /// <param name="name">The name of the method.</param>
 844    /// <param name="parameters">An enumeration of the parameters of the method.  Optional.</param>
 845    /// <param name="summary">The summary of the method.  Optional.</param>
 846    /// <param name="remarks">The remarks about the method.  Optional.</param>
 847    /// <param name="returnDescription">A description of the return value.  Optional.</param>
 848    /// <param name="typeParameters">An enumeration of the type parameters.  Optional.</param>
 849    public MethodDocumentation(string name, IEnumerable<ParameterDocumentation>? parameters, string? summary, string? re
 850    {
 851        Name = name;
 852        Parameters = parameters;
 853        Summary = summary;
 854        Remarks = remarks;
 855        ReturnDescription = returnDescription;
 856        TypeParameters = typeParameters;
 857    }
 858}
 859/// <summary>
 860/// An immutable class that contains documentation for a member.
 861/// </summary>
 862public class MemberDocumentation
 863{
 864    /// <summary>
 865    /// The name of the member.
 866    /// </summary>
 867    public string Name { get; private set; }
 868    /// <summary>
 869    /// The summary specified in the xml documentation comments.
 870    /// </summary>
 871    public string? Summary { get; private set; }
 872    /// <summary>
 873    /// The remarks specified in the xml documentation comments.
 874    /// </summary>
 875    public string? Remarks { get; private set; }
 876
 877    /// <summary>
 878    /// Constructs a MemberDocumentation instance containing the specified documentation.
 879    /// </summary>
 880    /// <param name="name">The name of the member.</param>
 881    /// <param name="summary">A summary of the member.</param>
 882    /// <param name="remarks">A description of the member.</param>
 883    public MemberDocumentation(string name, string? summary, string? remarks)
 884    {
 885        Name = name;
 886        Summary = summary;
 887        Remarks = remarks;
 888    }
 889}
 890#endif
 891
 892/// <summary>
 893/// An attribute that causes the documentation generator to replace the annotated type with the specified type to compen
 894/// </summary>
 895[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class)]
 896public sealed class ProxyTypeAttribute : Attribute
 897{
 898    /// <summary>
 899    /// Constructs a JSON proxy type attribute which overrides one type with another when documenting APIs, to compensat
 900    /// </summary>
 901    /// <param name="type">The <see cref="Type"/> to replace the type this attribute is applied to with.</param>
 902    public ProxyTypeAttribute(Type type)
 903    {
 904        Type = type;
 905    }
 906    /// <summary>
 907    /// Gets the <see cref="Type"/> to replace the annotated type with when documenting APIs.
 908    /// </summary>
 909    public Type Type { get; }
 910}
 911
 912/// <summary>
 913/// An attribute that indicates that another type, even if not explicitly referenced, should be included in the overall 
 914/// </summary>
 915[AttributeUsage(AttributeTargets.Module | AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Interface 
 916public sealed class IncludeTypeInDocumentationAttribute : Attribute
 917{
 918    /// <summary>
 919    /// Gets the type to add to the documentation.
 920    /// </summary>
 921    public Type Type { get; }
 922
 923    /// <summary>
 924    /// Constructs an override body type attribute.
 925    /// </summary>
 926    /// <param name="type">The type to put in the documentation as the body type.</param>
 927    public IncludeTypeInDocumentationAttribute(Type type)
 928    {
 929        Type = type;
 930    }
 931}
 932
 933/// <summary>
 934/// An attribute that indicates that the parameter, property, field, return value, or interface should NOT be included i
 935/// </summary>
 936[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.F
 937public sealed class ExcludeInDocumentationAttribute : Attribute
 938{
 939    /// <summary>
 940    /// Constructs an override body type attribute.
 941    /// </summary>
 2942    public ExcludeInDocumentationAttribute()
 943    {
 2944    }
 945}

Methods/Properties

.ctor()