Skip to content

fix humanize for multiline code and <para> tag #3295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ public static string Humanize(string text, string xmlCommentEndOfLine)
.HumanizeRefTags()
.HumanizeHrefTags()
.HumanizeCodeTags()
.HumanizeMultilineCodeTags()
.HumanizeMultilineCodeTags(xmlCommentEndOfLine)
.HumanizeParaTags()
.HumanizeBrTags(xmlCommentEndOfLine) // must be called after HumanizeParaTags() so that it replaces any additional <br> tags
.DecodeXml();
}

private static string NormalizeIndentation(this string text, string xmlCommentEndOfLine)
{
string[] lines = text.Split('\n');
var lines = text.Split(["\r\n", "\n"], StringSplitOptions.None);
string padding = GetCommonLeadingWhitespace(lines);

int padLen = padding == null ? 0 : padding.Length;
int padLen = padding?.Length ?? 0;

// remove leading padding from each line
for (int i = 0, l = lines.Length; i < l; ++i)
Expand All @@ -51,7 +51,7 @@ private static string NormalizeIndentation(this string text, string xmlCommentEn

// remove leading empty lines, but not all leading padding
// remove all trailing whitespace, regardless
return string.Join(xmlCommentEndOfLine ?? "\r\n", lines.SkipWhile(x => string.IsNullOrWhiteSpace(x))).TrimEnd();
return string.Join(EndOfLine(xmlCommentEndOfLine), lines.SkipWhile(string.IsNullOrWhiteSpace)).TrimEnd();
}

private static string GetCommonLeadingWhitespace(string[] lines)
Expand Down Expand Up @@ -105,7 +105,7 @@ private static string HumanizeCodeTags(this string text)
return CodeTag().Replace(text, (match) => "`" + match.Groups["display"].Value + "`");
}

private static string HumanizeMultilineCodeTags(this string text)
private static string HumanizeMultilineCodeTags(this string text, string xmlCommentEndOfLine)
{
return MultilineCodeTag().Replace(text, match =>
{
Expand All @@ -115,12 +115,17 @@ private static string HumanizeMultilineCodeTags(this string text)
var builder = new StringBuilder().Append("```");
if (!codeText.StartsWith("\r") && !codeText.StartsWith("\n"))
{
builder.AppendLine();
builder.Append(EndOfLine(xmlCommentEndOfLine));
}

return builder.AppendLine(codeText.TrimEnd())
.Append("```")
.ToString();
builder.Append(RemoveCommonLeadingWhitespace(codeText, xmlCommentEndOfLine));
if (!codeText.EndsWith("\n"))
{
builder.Append(EndOfLine(xmlCommentEndOfLine));
}

builder.Append("```");
return DoubleUpLineBreaks().Replace(builder.ToString(), EndOfLine(xmlCommentEndOfLine));
}

return $"```{codeText}```";
Expand All @@ -129,30 +134,54 @@ private static string HumanizeMultilineCodeTags(this string text)

private static string HumanizeParaTags(this string text)
{
return ParaTag().Replace(text, match =>
{
var paraText = "<br>" + match.Groups["display"].Value.Trim();
return LineBreaks().Replace(paraText, _ => string.Empty);
});
return ParaTag().Replace(text, match => "<br>" + match.Groups["display"].Value.Trim());
}

private static string HumanizeBrTags(this string text, string xmlCommentEndOfLine)
{
return BrTag().Replace(text, _ => xmlCommentEndOfLine ?? Environment.NewLine);
return BrTag().Replace(text, _ => EndOfLine(xmlCommentEndOfLine));
}

private static string DecodeXml(this string text)
{
return WebUtility.HtmlDecode(text);
}

private static string RemoveCommonLeadingWhitespace(string input, string xmlCommentEndOfLine)
{
var lines = input.Split(["\r\n", "\n"], StringSplitOptions.None);
var padding = GetCommonLeadingWhitespace(lines);
if (string.IsNullOrEmpty(padding))
{
return input;
}

var minLeadingSpaces = padding.Length;
var builder = new StringBuilder();
foreach (var line in lines)
{
builder.Append(string.IsNullOrWhiteSpace(line)
? line
: line.Substring(minLeadingSpaces));
builder.Append(EndOfLine(xmlCommentEndOfLine));
}

return builder.ToString();
}

internal static string EndOfLine(string xmlCommentEndOfLine)
{
return xmlCommentEndOfLine ?? Environment.NewLine;
}

private const string RefTagPattern = @"<(see|paramref) (name|cref|langword)=""([TPF]{1}:)?(?<display>.+?)"" ?/>";
private const string CodeTagPattern = @"<c>(?<display>.+?)</c>";
private const string MultilineCodeTagPattern = @"<code>(?<display>.+?)</code>";
private const string ParaTagPattern = @"<para>(?<display>.+?)</para>";
private const string HrefPattern = @"<see href=\""(.*)\"">(.*)<\/see>";
private const string BrPattern = @"(<br ?\/?>)"; // handles <br>, <br/>, <br />
private const string LineBreaksPattern = @"\r?\n";
private const string DoubleUpLineBreaksPattern = @"(\r?\n){2,}";

#if NET7_0_OR_GREATER
[GeneratedRegex(RefTagPattern)]
Expand All @@ -175,6 +204,9 @@ private static string DecodeXml(this string text)

[GeneratedRegex(LineBreaksPattern)]
private static partial Regex LineBreaks();

[GeneratedRegex(DoubleUpLineBreaksPattern)]
private static partial Regex DoubleUpLineBreaks();
#else
private static readonly Regex _refTag = new(RefTagPattern);
private static readonly Regex _codeTag = new(CodeTagPattern);
Expand All @@ -183,6 +215,7 @@ private static string DecodeXml(this string text)
private static readonly Regex _hrefTag = new(HrefPattern);
private static readonly Regex _brTag = new(BrPattern);
private static readonly Regex _lineBreaks = new(LineBreaksPattern);
private static readonly Regex _doubleUpLineBreaks = new(DoubleUpLineBreaksPattern);

private static Regex RefTag() => _refTag;
private static Regex CodeTag() => _codeTag;
Expand All @@ -191,6 +224,7 @@ private static string DecodeXml(this string text)
private static Regex HrefTag() => _hrefTag;
private static Regex BrTag() => _brTag;
private static Regex LineBreaks() => _lineBreaks;
private static Regex DoubleUpLineBreaks() => _doubleUpLineBreaks;
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
"CrudActions"
],
"summary": "Get all products",
"description": "```\r\n {\r\n \"Id\":1,\r\n \"Description\":\"\",\r\n \"Status\": 0,\r\n \"Status2\": 1\r\n }\r\n```",
"description": "```\r\n{\r\n \"Id\":1,\r\n \"Description\":\"\",\r\n \"Status\": 0,\r\n \"Status2\": 1\r\n}\r\n \r\n```",
"responses": {
"200": {
"description": "OK",
Expand Down Expand Up @@ -199,7 +199,7 @@
"CrudActions"
],
"summary": "Updates some properties of a specific product",
"description": "\r\nOnly provided properties will be updated, other remain unchanged.\r\n\r\nIdentifier must be non-default value\r\n\r\nBody must be specified",
"description": "\r\nOnly provided properties will be updated,\r\n other remain unchanged.\r\n\r\nIdentifier must be non-default value\r\n\r\nBody must be specified",
"operationId": "PatchProduct",
"parameters": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
"CrudActions"
],
"summary": "Get all products",
"description": "```\r\n {\r\n \"Id\":1,\r\n \"Description\":\"\",\r\n \"Status\": 0,\r\n \"Status2\": 1\r\n }\r\n```",
"description": "```\r\n{\r\n \"Id\":1,\r\n \"Description\":\"\",\r\n \"Status\": 0,\r\n \"Status2\": 1\r\n}\r\n \r\n```",
"responses": {
"200": {
"description": "OK",
Expand Down Expand Up @@ -199,7 +199,7 @@
"CrudActions"
],
"summary": "Updates some properties of a specific product",
"description": "\r\nOnly provided properties will be updated, other remain unchanged.\r\n\r\nIdentifier must be non-default value\r\n\r\nBody must be specified",
"description": "\r\nOnly provided properties will be updated,\r\n other remain unchanged.\r\n\r\nIdentifier must be non-default value\r\n\r\nBody must be specified",
"operationId": "PatchProduct",
"parameters": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using Xunit;
using Xunit;

namespace Swashbuckle.AspNetCore.SwaggerGen.Test
{
Expand Down Expand Up @@ -145,29 +144,10 @@ public void Humanize_HumanizesInlineTags(
Assert.Equal(expectedOutput, output, false, true);
}

[Fact]
public void Humanize_MultilineBrTag_EolNotSpecified()
{
const string input = @"
This is a paragraph.
<br>
A parameter after br tag.";

var output = XmlCommentsTextHelper.Humanize(input);

// Result view for Linux: This is a paragraph.\r\n\n\r\nA parameter after br tag.
var expected = string.Join("\r\n",
[
"This is a paragraph.",
Environment.NewLine,
"A parameter after br tag."
]);
Assert.Equal(expected, output, false, ignoreLineEndingDifferences: false);
}

[Theory]
[InlineData("\r\n")]
[InlineData("\n")]
[InlineData(null)]
public void Humanize_MultilineBrTag_SpecificEol(string xmlCommentEndOfLine)
{
const string input = @"
Expand All @@ -177,7 +157,7 @@ This is a paragraph.

var output = XmlCommentsTextHelper.Humanize(input, xmlCommentEndOfLine);

var expected = string.Join(xmlCommentEndOfLine,
var expected = string.Join(XmlCommentsTextHelper.EndOfLine(xmlCommentEndOfLine),
[
"This is a paragraph.",
"",
Expand All @@ -199,7 +179,7 @@ This is a paragraph.

var output = XmlCommentsTextHelper.Humanize(input);

Assert.Equal("\r\nThis is a paragraph. MultiLined.\r\n\r\nThis is a paragraph.", output, false, true);
Assert.Equal("\r\nThis is a paragraph.\r\n MultiLined.\r\n\r\nThis is a paragraph.", output, false, true);
}

[Fact]
Expand All @@ -215,16 +195,16 @@ public void Humanize_CodeMultiLineTag()

var output = XmlCommentsTextHelper.Humanize(input);

var expected = string.Join("\r\n",
var expected = string.Join(XmlCommentsTextHelper.EndOfLine(null),
[
"```",
" {",
" \"Prop1\":1,",
" \"Prop2\":[]",
" }",
"{",
" \"Prop1\":1,",
" \"Prop2\":[]",
"}",
"```"
]);
Assert.Equal(expected, output, false, true);
Assert.Equal(expected, output);
}

[Fact]
Expand All @@ -239,7 +219,7 @@ public void Humanize_CodeMultiLineTag_OnSameLine()

var output = XmlCommentsTextHelper.Humanize(input);

var expected = string.Join("\r\n",
var expected = string.Join(XmlCommentsTextHelper.EndOfLine(null),
[
"```",
"{",
Expand All @@ -248,7 +228,52 @@ public void Humanize_CodeMultiLineTag_OnSameLine()
" }",
"```"
]);
Assert.Equal(expected, output, false, true);
Assert.Equal(expected, output);
}

[Fact]
public void Humanize_CodeInsideParaTag()
{
var input = string.Join(XmlCommentsTextHelper.EndOfLine(null),
[
"<para>Creates a new Answer</para>",
"<para><code><![CDATA[",
"POST /api/answers",
"{",
""" "name": "OnlyYes",""",
""" "label": "Yes",""",
""" "answers": [""",
" {",
""" "answer": "yes""",
" }",
" ]",
"}",
"]]></code></para>",
]);

var output = XmlCommentsTextHelper.Humanize(input);

var expected = string.Join(XmlCommentsTextHelper.EndOfLine(null),
[
"",
"Creates a new Answer",
"",
"```",
"<![CDATA[",
"POST /api/answers",
"{",
""" "name": "OnlyYes",""",
""" "label": "Yes",""",
""" "answers": [""",
" {",
""" "answer": "yes""",
" }",
" ]",
"}",
"]]>",
"```"
]);
Assert.Equal(expected, output);
}
}
}