Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions global.json

This file was deleted.

2 changes: 1 addition & 1 deletion src/XmlDocMarkdown.Core/ArgsReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal sealed class ArgsReader
/// <exception cref="ArgumentNullException"><c>args</c> is <c>null</c>.</exception>
public ArgsReader(IEnumerable<string> args)
{
m_args = (args ?? throw new ArgumentNullException(nameof(args))).ToList();
m_args = [.. args ?? throw new ArgumentNullException(nameof(args))];
}

/// <summary>
Expand Down
94 changes: 84 additions & 10 deletions src/XmlDocMarkdown.Core/MarkdownGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace XmlDocMarkdown.Core
{
Expand All @@ -31,7 +32,7 @@ internal sealed class MarkdownGenerator
public IReadOnlyList<ExternalDocumentation>? ExternalDocs { get; set; }

public IReadOnlyList<NamedText> GenerateOutput(Assembly assembly, XmlDocAssembly xmlDocAssembly) =>
DoGenerateOutput(assembly, xmlDocAssembly).ToList();
[.. DoGenerateOutput(assembly, xmlDocAssembly)];

public static string GetCodeGenComment(string assemblyName) => $"<!-- DO NOT EDIT: generated by xmldocmd for {assemblyName} -->";

Expand Down Expand Up @@ -326,7 +327,7 @@ private string GetPermalink(string path)
return path.Replace("\\", "/");
}

private NamedText WriteMemberPage(string path, string parent, string title, IReadOnlyList<MemberInfo> memberGroup, MarkdownContext context)
private NamedText WriteMemberPage(string path, string parent, string title, List<MemberInfo> memberGroup, MarkdownContext context)
{
var extension = GetFileExtension();

Expand Down Expand Up @@ -682,15 +683,15 @@ private bool IsVisible(MemberInfo memberInfo)

public static XmlDocVisibilityLevel GetMostPrivate(params XmlDocVisibilityLevel[] visibilityLevels) => (XmlDocVisibilityLevel) visibilityLevels.Min(x => (int) x);

private static string GetMemberHeading(IReadOnlyList<MemberInfo> membersInfos, int index)
private static string GetMemberHeading(List<MemberInfo> membersInfos, int index)
{
var heading = $"{GetFullMemberName(membersInfos[index])} {GetMemberGroupNoun([membersInfos[index]])}";
if (membersInfos.Count > 1)
heading += $" ({index + 1} of {membersInfos.Count})";
return heading;
}

private static string GetMemberGroupNoun(IReadOnlyList<MemberInfo> memberInfos)
private static string GetMemberGroupNoun(List<MemberInfo> memberInfos)
{
var plural = memberInfos.Count != 1;

Expand All @@ -709,11 +710,11 @@ private static string GetMemberGroupNoun(IReadOnlyList<MemberInfo> memberInfos)
if (memberInfos.All(x => x is FieldInfo))
return plural ? "fields" : "field";
if (memberInfos.All(x => x is TypeInfo))
return GetTypeGroupNoun(memberInfos.Cast<TypeInfo>().ToList());
return GetTypeGroupNoun([.. memberInfos.Cast<TypeInfo>()]);
return plural ? "members" : "member";
}

private static string GetTypeGroupNoun(IReadOnlyList<TypeInfo> typeInfos)
private static string GetTypeGroupNoun(List<TypeInfo> typeInfos)
{
var plural = typeInfos.Count != 1;

Expand Down Expand Up @@ -1250,7 +1251,7 @@ private IEnumerable<string> GetFullSignatureParts(MemberInfo memberInfo, ICollec

yield return RenderTypeName(parameterInfo.ParameterType.GetTypeInfo(),
seeAlso: seeAlsoMembers,
attributes: parameterInfo.GetCustomAttributes().ToList(),
attributes: [.. parameterInfo.GetCustomAttributes()],
nullableContextFlags: nullableContextFlags);

yield return " ";
Expand Down Expand Up @@ -1974,7 +1975,7 @@ private static Type[] GetGenericArguments(MemberInfo memberInfo)
return type.GenericTypeParameters;

var method = memberInfo as MethodInfo;
return method?.GetGenericArguments() ?? Array.Empty<Type>();
return method?.GetGenericArguments() ?? [];
}

private static ParameterInfo[] GetParameters(MemberInfo memberInfo)
Expand All @@ -1988,7 +1989,7 @@ private static ParameterInfo[] GetParameters(MemberInfo memberInfo)
return propertyInfo.GetIndexParameters();

var method = memberInfo as MethodBase;
return method?.GetParameters() ?? Array.Empty<ParameterInfo>();
return method?.GetParameters() ?? [];
}

private static string GetParameterShortNames(MemberInfo memberInfo) =>
Expand Down Expand Up @@ -2119,7 +2120,7 @@ private string MakeRelative(string baseUri, string path)
private string? ToMarkdown(IEnumerable<XmlDocInline>? inlines, MarkdownContext context) =>
inlines == null ? null : string.Concat(inlines.Select(x => ToMarkdown(x, context))).Trim();

private IEnumerable<string> ToMarkdown(IReadOnlyList<XmlDocBlock> blocks, MarkdownContext context)
private IEnumerable<string> ToMarkdown(Collection<XmlDocBlock> blocks, MarkdownContext context)
{
for (var index = 0; index < blocks.Count; index++)
{
Expand Down Expand Up @@ -2158,6 +2159,61 @@ private IEnumerable<string> ToMarkdown(IReadOnlyList<XmlDocBlock> blocks, Markdo
index++;
}
}
else if (block.ListKind == XmlDocListKind.Table)
{
if (string.IsNullOrWhiteSpace(block.Inlines[0].Text))
throw new ArgumentException("Input XML cannot be null or empty.", nameof(blocks));

if (block.Inlines[0].Text is { } text)
{
var doc = XDocument.Parse(text);
var table = (doc.Root?.Name.LocalName == "table"
? doc.Root
: doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "table")) ?? throw new InvalidOperationException("No <table> element was found.");
var thead = table.Elements().FirstOrDefault(e => e.Name.LocalName == "thead");
var tbody = table.Elements().FirstOrDefault(e => e.Name.LocalName == "tbody");

var headerRow = thead?.Descendants().FirstOrDefault(e => e.Name.LocalName == "tr");
var headers = headerRow != null ? GetCells(headerRow) : new List<string>();

// Fallback: if there is no <thead>, use first data row as header.
var bodyRows = (tbody?.Elements().Where(e => e.Name.LocalName == "tr")
?? table.Elements().Where(e => e.Name.LocalName == "tr")).ToList();

if (headers.Count == 0 && bodyRows.Count > 0)
{
headers = GetCells(bodyRows[0]);
bodyRows.RemoveAt(0);
}

if (headers.Count == 0)
throw new InvalidOperationException("No header row could be determined.");

var sb = new StringBuilder();

sb.AppendLine("| " + string.Join(" | ", headers.Select(EscapeCell)) + " |");
sb.AppendLine("| " + string.Join(" | ", headers.Select(_ => "---")) + " |");

foreach (var cells in bodyRows.Select(GetCells))
{
var list = cells;

// Keep markdown shape stable if rows have fewer/more columns.
if (cells.Count < headers.Count)
{
list.AddRange(Enumerable.Repeat("", headers.Count - cells.Count));
}
else if (cells.Count > headers.Count)
{
list = [.. cells.Take(headers.Count)];
}

sb.AppendLine("| " + string.Join(" | ", list.Select(EscapeCell)) + " |");
}

yield return sb.ToString();
}
}
else
{
if (block.IsCode)
Expand All @@ -2178,6 +2234,24 @@ private IEnumerable<string> ToMarkdown(IReadOnlyList<XmlDocBlock> blocks, Markdo
}
}

private static List<string> GetCells(XElement row) =>
[
.. row.Elements()
.Where(e => e.Name.LocalName == "th" || e.Name.LocalName == "td")
.Select(e => NormalizeWhitespace(e.Value))
];

private static string NormalizeWhitespace(string value) =>
Regex.Replace(value ?? "", @"\s+", " ").Trim();

private static string EscapeCell(string value)
{
value ??= "";
value = value.Replace("|", "\\|");
value = value.Replace("\r\n", "<br/>").Replace("\n", "<br/>");
return value;
}

private sealed class MarkdownContext
{
public MarkdownContext(XmlDocAssembly xmlDocAssembly, IReadOnlyDictionary<string, MemberInfo> membersByXmlDocName, string assemblyFileName, string? sourceCodePath, string rootNamespace, string pageLocation)
Expand Down
11 changes: 10 additions & 1 deletion src/XmlDocMarkdown.Core/XmlDocMember.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,15 @@ private void AddElement(XElement xElement)
m_block?.Inlines.Add(new XmlDocInline { Text = (string) xElement.Attribute("name"), IsTypeParamRef = true });
break;

case "table":
// Insert the raw table
m_listKinds.Push(XmlDocListKind.Table);
NextBlock();
m_block?.Inlines.Add(new XmlDocInline { Text = xElement.ToString() });
m_listKinds.Pop();
NextBlock();
break;

default:
AddNodes(xElement.Nodes());
break;
Expand Down Expand Up @@ -241,7 +250,7 @@ private static XmlDocListKind GetListKind(XElement xElement) =>
private static string TrimCode(string text)
{
// trimming logic adapted from https://github.com/kzu/NuDoq
var lines = text.Split(new[] { Environment.NewLine, "\n" }, StringSplitOptions.None).ToList();
var lines = text.Split([Environment.NewLine, "\n"], StringSplitOptions.None).ToList();

if (lines.Count != 0 && lines[0].Trim().Length == 0)
lines.RemoveAt(0);
Expand Down
2 changes: 1 addition & 1 deletion src/XmlDocMarkdown.Core/XmlDocToc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private XmlDocToc GetOrCreate(string path, string title)

internal void Save(string tocPath)
{
Directory.CreateDirectory(System.IO.Path.GetDirectoryName(tocPath)!);
Directory.CreateDirectory(System.IO.Path.GetDirectoryName(tocPath) ?? "");
using (var writer = new StreamWriter(tocPath, false, Encoding.UTF8))
{
writer.WriteLine("toc:");
Expand Down
2 changes: 1 addition & 1 deletion tests/XmlDocMarkdown.Tests/MarkdownGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace XmlDocMarkdown.Tests
{
public class MarkdownGeneratorTests
internal sealed class MarkdownGeneratorTests
{
[Test]
public void ExampleAssembly()
Expand Down
2 changes: 2 additions & 0 deletions tools/ExampleAssembly/IsExternalInit.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.ComponentModel;

#pragma warning disable IDE0130
namespace System.Runtime.CompilerServices
#pragma warning restore IDE0130
{
/// <summary>
/// Bug fix for C# 9 record when not using .NET 5.
Expand Down