diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsTextHelper.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsTextHelper.cs
index 7903a3e2ed..9c19354db6 100644
--- a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsTextHelper.cs
+++ b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsTextHelper.cs
@@ -25,7 +25,7 @@ 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
tags
.DecodeXml();
@@ -33,10 +33,10 @@ public static string Humanize(string text, string xmlCommentEndOfLine)
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)
@@ -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)
@@ -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 =>
{
@@ -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}```";
@@ -129,16 +134,12 @@ private static string HumanizeMultilineCodeTags(this string text)
private static string HumanizeParaTags(this string text)
{
- return ParaTag().Replace(text, match =>
- {
- var paraText = "
" + match.Groups["display"].Value.Trim();
- return LineBreaks().Replace(paraText, _ => string.Empty);
- });
+ return ParaTag().Replace(text, match => "
" + 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)
@@ -146,6 +147,33 @@ 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}:)?(?.+?)"" ?/>";
private const string CodeTagPattern = @"(?.+?)";
private const string MultilineCodeTagPattern = @"(?.+?)";
@@ -153,6 +181,7 @@ private static string DecodeXml(this string text)
private const string HrefPattern = @"(.*)<\/see>";
private const string BrPattern = @"(
)"; // handles
,
,
private const string LineBreaksPattern = @"\r?\n";
+ private const string DoubleUpLineBreaksPattern = @"(\r?\n){2,}";
#if NET7_0_OR_GREATER
[GeneratedRegex(RefTagPattern)]
@@ -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);
@@ -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;
@@ -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
}
}
diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_DotNet6_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_DotNet6_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt
index 9ba7fd7080..360844ea55 100644
--- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_DotNet6_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt
+++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_DotNet6_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt
@@ -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",
@@ -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": [
{
diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt
index d569ed88c2..32e5af36f3 100644
--- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt
+++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_startupType=Basic.Startup_swaggerRequestUri=v1.verified.txt
@@ -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",
@@ -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": [
{
diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsTextHelperTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsTextHelperTests.cs
index cf554a59d1..481c96a79d 100644
--- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsTextHelperTests.cs
+++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/XmlComments/XmlCommentsTextHelperTests.cs
@@ -1,5 +1,4 @@
-using System;
-using Xunit;
+using Xunit;
namespace Swashbuckle.AspNetCore.SwaggerGen.Test
{
@@ -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.
-
- 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 = @"
@@ -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.",
"",
@@ -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]
@@ -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]
@@ -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),
[
"```",
"{",
@@ -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),
+ [
+ "Creates a new Answer",
+ "",
+ ]);
+
+ var output = XmlCommentsTextHelper.Humanize(input);
+
+ var expected = string.Join(XmlCommentsTextHelper.EndOfLine(null),
+ [
+ "",
+ "Creates a new Answer",
+ "",
+ "```",
+ "",
+ "```"
+ ]);
+ Assert.Equal(expected, output);
}
}
}