diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1623UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1623UnitTests.cs
index 0e6503f58..a5f6cd901 100644
--- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1623UnitTests.cs
+++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1623UnitTests.cs
@@ -345,5 +345,42 @@ public class TestClass
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(" XYZ")]
+ [WorkItem(3465, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3465")]
+ public async Task VerifyInheritdocInSummaryTagIsAllowedAsync(string summary)
+ {
+ var testCode = $@"
+public class TestClass
+{{
+ ///
+ /// {summary}
+ ///
+ public int TestProperty {{ get; set; }}
+}}
+";
+
+ await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ [Fact]
+ [WorkItem(3465, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3465")]
+ public async Task VerifyInheritdocAfterTextStillReportsWarningAsync()
+ {
+ var testCode = @"
+public class TestClass
+{
+ ///
+ /// XYZ
+ ///
+ public int {|#0:TestProperty|} { get; set; }
+}
+";
+
+ var expected = Diagnostic(PropertySummaryDocumentationAnalyzer.SA1623Descriptor).WithLocation(0).WithArguments("Gets or sets");
+ await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ }
}
}
diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1624UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1624UnitTests.cs
index fb9ce294e..8cf5f174f 100644
--- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1624UnitTests.cs
+++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1624UnitTests.cs
@@ -156,5 +156,42 @@ public class TestClass
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(" XYZ")]
+ [WorkItem(3465, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3465")]
+ public async Task VerifyInheritdocInSummaryTagIsAllowedAsync(string summary)
+ {
+ var testCode = $@"
+public class TestClass
+{{
+ ///
+ /// {summary}
+ ///
+ public int TestProperty {{ get; private set; }}
+}}
+";
+
+ await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ [Fact]
+ [WorkItem(3465, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3465")]
+ public async Task VerifyInheritdocAfterTextStillReportsWarningAsync()
+ {
+ var testCode = @"
+public class TestClass
+{
+ ///
+ /// XYZ
+ ///
+ public int {|#0:TestProperty|} { get; private set; }
+}
+";
+
+ var expected = Diagnostic(PropertySummaryDocumentationAnalyzer.SA1623Descriptor).WithLocation(0).WithArguments("Gets");
+ await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
+ }
}
}
diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/PropertySummaryDocumentationAnalyzer.cs b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/PropertySummaryDocumentationAnalyzer.cs
index 0580c4aad..9170d7367 100644
--- a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/PropertySummaryDocumentationAnalyzer.cs
+++ b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/PropertySummaryDocumentationAnalyzer.cs
@@ -61,6 +61,18 @@ internal class PropertySummaryDocumentationAnalyzer : PropertyDocumentationBase
///
protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, StyleCopSettings settings, bool needsComment, XmlNodeSyntax syntax, XElement completeDocumentation, Location diagnosticLocation)
{
+ if (!(syntax is XmlElementSyntax summaryElement))
+ {
+ // This is reported by SA1604 or SA1606.
+ return;
+ }
+
+ if (SummaryStartsWithInheritdoc(summaryElement))
+ {
+ // Ignore nodes starting with an tag.
+ return;
+ }
+
var propertyDeclaration = (PropertyDeclarationSyntax)context.Node;
var propertyType = context.SemanticModel.GetTypeInfo(propertyDeclaration.Type.StripRefFromType());
var culture = settings.DocumentationRules.DocumentationCultureInfo;
@@ -70,7 +82,7 @@ protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, Styl
{
AnalyzeSummaryElement(
context,
- syntax,
+ summaryElement,
diagnosticLocation,
propertyDeclaration,
resourceManager.GetString(nameof(DocumentationResources.StartingTextGetsWhether), culture),
@@ -82,7 +94,7 @@ protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, Styl
{
AnalyzeSummaryElement(
context,
- syntax,
+ summaryElement,
diagnosticLocation,
propertyDeclaration,
resourceManager.GetString(nameof(DocumentationResources.StartingTextGets), culture),
@@ -92,7 +104,7 @@ protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, Styl
}
}
- private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, Location diagnosticLocation, PropertyDeclarationSyntax propertyDeclaration, string startingTextGets, string startingTextSets, string startingTextGetsOrSets, string startingTextReturns)
+ private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, XmlElementSyntax summaryElement, Location diagnosticLocation, PropertyDeclarationSyntax propertyDeclaration, string startingTextGets, string startingTextSets, string startingTextGetsOrSets, string startingTextReturns)
{
var diagnosticProperties = ImmutableDictionary.CreateBuilder();
ArrowExpressionClauseSyntax expressionBody = propertyDeclaration.ExpressionBody;
@@ -116,12 +128,6 @@ private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, Xml
}
}
- if (!(syntax is XmlElementSyntax summaryElement))
- {
- // This is reported by SA1604 or SA1606.
- return;
- }
-
// Add a no code fix tag when the summary element is empty.
// This will only impact SA1623, because SA1624 cannot trigger with an empty summary.
if (summaryElement.Content.Count == 0)
@@ -284,6 +290,73 @@ private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, Xml
}
}
+ private static bool SummaryStartsWithInheritdoc(XmlElementSyntax summaryElement)
+ {
+ foreach (var child in summaryElement.Content)
+ {
+ var firstContent = GetFirstMeaningfulChild(child);
+ if (firstContent is null)
+ {
+ continue;
+ }
+
+ return string.Equals(firstContent.GetName()?.ToString(), XmlCommentHelper.InheritdocXmlTag, StringComparison.Ordinal);
+ }
+
+ return false;
+ }
+
+ private static XmlNodeSyntax GetFirstMeaningfulChild(XmlNodeSyntax node)
+ {
+ switch (node)
+ {
+ case XmlTextSyntax textSyntax:
+ foreach (var token in textSyntax.TextTokens)
+ {
+ if (!string.IsNullOrWhiteSpace(token.ValueText))
+ {
+ return textSyntax;
+ }
+ }
+
+ return null;
+
+ case XmlEmptyElementSyntax emptyElement:
+ return emptyElement;
+
+ case XmlElementSyntax elementSyntax:
+ if (string.Equals(elementSyntax.StartTag?.Name?.ToString(), XmlCommentHelper.InheritdocXmlTag, StringComparison.Ordinal))
+ {
+ return elementSyntax;
+ }
+
+ foreach (var child in elementSyntax.Content)
+ {
+ var nested = GetFirstMeaningfulChild(child);
+ if (nested != null)
+ {
+ return nested;
+ }
+ }
+
+ return null;
+
+ case XmlCDataSectionSyntax cdataSyntax:
+ foreach (var token in cdataSyntax.TextTokens)
+ {
+ if (!string.IsNullOrWhiteSpace(token.ValueText))
+ {
+ return cdataSyntax;
+ }
+ }
+
+ return null;
+
+ default:
+ return null;
+ }
+ }
+
private static void ReportSA1623(SyntaxNodeAnalysisContext context, Location diagnosticLocation, ImmutableDictionary.Builder diagnosticProperties, string text, string expectedStartingText, string unexpectedStartingText1, string unexpectedStartingText2 = null, string unexpectedStartingText3 = null)
{
diagnosticProperties.Add(ExpectedTextKey, expectedStartingText);