Skip to content

Commit 14245e2

Browse files
authored
Merge pull request #3371 from jnm2/allow_full_sentence_links
SA1629 should allow full-sentence links instead of forcing the period to glow white
2 parents a69732f + 0896aa6 commit 14245e2

File tree

3 files changed

+66
-8
lines changed

3 files changed

+66
-8
lines changed

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1629UnitTests.cs

+17
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,23 @@ public interface ITest
546546
await VerifyCSharpFixAsync(testCode, expected, fixedTestCode, default).ConfigureAwait(false);
547547
}
548548

549+
[Theory]
550+
[InlineData("a", true)]
551+
[InlineData("see", true)]
552+
[InlineData("seealso", false)]
553+
public async Task TestFullSentenceLinkAsync(string tag, bool insideSummary)
554+
{
555+
var surrounding = insideSummary ? (Start: "<summary>", End: "<summary>") : (Start: string.Empty, End: string.Empty);
556+
557+
var testCode = $@"
558+
/// {surrounding.Start}<{tag} href=""someurl"">Periods aren't required to glow white at the end of a full-sentence link.</{tag}>{surrounding.End}
559+
public interface ITest
560+
{{
561+
}}
562+
";
563+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, default).ConfigureAwait(false);
564+
}
565+
549566
[Theory]
550567
[InlineData(",")]
551568
[InlineData(";")]

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1629DocumentationTextMustEndWithAPeriod.cs

+18-8
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,7 @@ private static void HandleSectionOrBlockXmlElement(SyntaxNodeAnalysisContext con
127127

128128
if (!string.IsNullOrEmpty(textWithoutTrailingWhitespace))
129129
{
130-
if (!textWithoutTrailingWhitespace.EndsWith(".", StringComparison.Ordinal)
131-
&& !textWithoutTrailingWhitespace.EndsWith(".)", StringComparison.Ordinal)
132-
&& (startingWithFinalParagraph || !textWithoutTrailingWhitespace.EndsWith(":", StringComparison.Ordinal))
133-
&& !textWithoutTrailingWhitespace.EndsWith("-or-", StringComparison.Ordinal))
130+
if (IsMissingRequiredPeriod(textWithoutTrailingWhitespace, startingWithFinalParagraph))
134131
{
135132
int spanStart = textToken.SpanStart + textWithoutTrailingWhitespace.Length;
136133
ImmutableDictionary<string, string> properties = null;
@@ -164,10 +161,15 @@ void SetReplaceChar()
164161
}
165162
else if (xmlElement.Content[i].IsInlineElement() && !currentParagraphDone)
166163
{
167-
// Treat empty XML elements as a "word not ending with a period"
168-
var location = Location.Create(xmlElement.SyntaxTree, new TextSpan(xmlElement.Content[i].Span.End, 1));
169-
context.ReportDiagnostic(Diagnostic.Create(Descriptor, location));
170-
currentParagraphDone = true;
164+
var lastTextElement = XmlCommentHelper.TryGetLastTextElementWithContent(xmlElement.Content[i]);
165+
166+
if (lastTextElement is null // Treat empty XML elements as a "word not ending with a period"
167+
|| IsMissingRequiredPeriod(lastTextElement.TextTokens.Last().Text.TrimEnd(' ', '\r', '\n'), startingWithFinalParagraph))
168+
{
169+
var location = Location.Create(xmlElement.SyntaxTree, new TextSpan(xmlElement.Content[i].Span.End, 1));
170+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, location));
171+
currentParagraphDone = true;
172+
}
171173
}
172174
else if (xmlElement.Content[i] is XmlElementSyntax childXmlElement)
173175
{
@@ -200,5 +202,13 @@ void SetReplaceChar()
200202
}
201203
}
202204
}
205+
206+
private static bool IsMissingRequiredPeriod(string textWithoutTrailingWhitespace, bool startingWithFinalParagraph)
207+
{
208+
return !textWithoutTrailingWhitespace.EndsWith(".", StringComparison.Ordinal)
209+
&& !textWithoutTrailingWhitespace.EndsWith(".)", StringComparison.Ordinal)
210+
&& (startingWithFinalParagraph || !textWithoutTrailingWhitespace.EndsWith(":", StringComparison.Ordinal))
211+
&& !textWithoutTrailingWhitespace.EndsWith("-or-", StringComparison.Ordinal);
212+
}
203213
}
204214
}

StyleCop.Analyzers/StyleCop.Analyzers/Helpers/XmlCommentHelper.cs

+31
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,37 @@ internal static XmlTextSyntax TryGetFirstTextElementWithContent(XmlNodeSyntax no
192192
return null;
193193
}
194194

195+
/// <summary>
196+
/// Returns the last <see cref="XmlTextSyntax"/> which is not simply empty or whitespace.
197+
/// </summary>
198+
/// <param name="node">The XML content to search.</param>
199+
/// <returns>The last <see cref="XmlTextSyntax"/> which is not simply empty or whitespace, or
200+
/// <see langword="null"/> if no such element exists.</returns>
201+
internal static XmlTextSyntax TryGetLastTextElementWithContent(XmlNodeSyntax node)
202+
{
203+
if (node is XmlEmptyElementSyntax)
204+
{
205+
return null;
206+
}
207+
else if (node is XmlTextSyntax xmlText)
208+
{
209+
return !IsConsideredEmpty(node) ? xmlText : null;
210+
}
211+
else if (node is XmlElementSyntax xmlElement)
212+
{
213+
for (var i = xmlElement.Content.Count - 1; i >= 0; i--)
214+
{
215+
var nestedContent = TryGetFirstTextElementWithContent(xmlElement.Content[i]);
216+
if (nestedContent != null)
217+
{
218+
return nestedContent;
219+
}
220+
}
221+
}
222+
223+
return null;
224+
}
225+
195226
/// <summary>
196227
/// Checks if a <see cref="SyntaxTrivia"/> contains a <see cref="DocumentationCommentTriviaSyntax"/> and returns
197228
/// <see langword="true"/> if it is considered empty.

0 commit comments

Comments
 (0)