Skip to content

Commit 9de3cb3

Browse files
committed
Updated SA1627
1 parent 807e7b3 commit 9de3cb3

2 files changed

Lines changed: 185 additions & 3 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1627UnitTests.cs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ namespace StyleCop.Analyzers.Test.DocumentationRules
77
using System.Threading;
88
using System.Threading.Tasks;
99
using Analyzers.DocumentationRules;
10+
using Microsoft.CodeAnalysis;
1011
using Microsoft.CodeAnalysis.Diagnostics;
12+
using StyleCop.Analyzers.Test.Helpers;
1113
using TestHelper;
1214
using Xunit;
1315

@@ -144,6 +146,149 @@ public class ClassName
144146
await this.VerifyCSharpDiagnosticAsync(testCode.Replace("$$", element), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
145147
}
146148

149+
/// <summary>
150+
/// Verifies that member with valid elements in the included documentation does not produce diagnostics.
151+
/// </summary>
152+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
153+
[Fact]
154+
public async Task TestMemberWithValidElementsInIncludedDocumentationAsync()
155+
{
156+
var testCode = @"
157+
/// <summary>Test class</summary>
158+
public class TestClass
159+
{
160+
/// <include file='AllFilled.xml' path='/TestClass/TestMethod/*'/>
161+
public void TestMethod(int first) { }
162+
}
163+
";
164+
165+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
166+
}
167+
168+
/// <summary>
169+
/// Verifies that member with an &lt;inheritdoc&gt; tag in the included documentation does not produce diagnostics.
170+
/// </summary>
171+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
172+
[Fact]
173+
public async Task TestMemberWithInheritDocInIncludedDocumentationAsync()
174+
{
175+
var testCode = @"
176+
/// <summary>Test class</summary>
177+
public class TestClass
178+
{
179+
/// <include file='InheritDoc.xml' path='/TestClass/TestMethod/*'/>
180+
public void TestMethod(int first) { }
181+
}
182+
";
183+
184+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
185+
}
186+
187+
/// <summary>
188+
/// Verifies that member with a single invalid elements in the included documentation does produce the expected diagnostics.
189+
/// </summary>
190+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
191+
[Fact]
192+
public async Task TestMemberWithOneInvalidElementsInIncludedDocumentationAsync()
193+
{
194+
var testCode = @"
195+
/// <summary>Test class</summary>
196+
public class TestClass
197+
{
198+
/// <include file='AllButOneFilled.xml' path='/TestClass/TestMethod/*'/>
199+
public void TestMethod(int first) { }
200+
}
201+
";
202+
var expected = this.CSharpDiagnostic().WithLocation(5, 9).WithArguments("permission");
203+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
204+
}
205+
206+
/// <summary>
207+
/// Verifies that member with multiple invalid elements in the included documentation does produce the expected diagnostics.
208+
/// </summary>
209+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
210+
[Fact]
211+
public async Task TestMemberWithMultipleInvalidElementsInIncludedDocumentationAsync()
212+
{
213+
var testCode = @"
214+
/// <summary>Test class</summary>
215+
public class TestClass
216+
{
217+
/// <include file='NoneFilled.xml' path='/TestClass/TestMethod/*'/>
218+
public void TestMethod(int first) { }
219+
}
220+
";
221+
222+
DiagnosticResult[] expected =
223+
{
224+
this.CSharpDiagnostic().WithLocation(5, 9).WithArguments("remarks"),
225+
this.CSharpDiagnostic().WithLocation(5, 9).WithArguments("example"),
226+
this.CSharpDiagnostic().WithLocation(5, 9).WithArguments("permission"),
227+
this.CSharpDiagnostic().WithLocation(5, 9).WithArguments("exception"),
228+
};
229+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
230+
}
231+
232+
protected override Project ApplyCompilationOptions(Project project)
233+
{
234+
var resolver = new TestXmlReferenceResolver();
235+
236+
string contentAllFilled = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
237+
<TestClass>
238+
<TestMethod>
239+
<summary>This is a test method.</summary>
240+
<param name=""first"">The first parameter.</param>
241+
<remarks>Test remarks</remarks>
242+
<example>Test example</example>
243+
<permission>Test permission</permission>
244+
<exception>Test exception</exception>
245+
</TestMethod>
246+
</TestClass>
247+
";
248+
resolver.XmlReferences.Add("AllFilled.xml", contentAllFilled);
249+
250+
string contentInheritDoc = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
251+
<TestClass>
252+
<TestMethod>
253+
<inheritdoc/>
254+
</TestMethod>
255+
</TestClass>
256+
";
257+
resolver.XmlReferences.Add("InheritDoc.xml", contentInheritDoc);
258+
259+
string contentAllButOneFilled = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
260+
<TestClass>
261+
<TestMethod>
262+
<summary>This is a test method.</summary>
263+
<param name=""first"">The first parameter.</param>
264+
<remarks>Test remarks</remarks>
265+
<example>Test example</example>
266+
<permission></permission>
267+
<exception>Test exception</exception>
268+
</TestMethod>
269+
</TestClass>
270+
";
271+
resolver.XmlReferences.Add("AllButOneFilled.xml", contentAllButOneFilled);
272+
273+
string contentNoneFilled = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
274+
<TestClass>
275+
<TestMethod>
276+
<summary>This is a test method.</summary>
277+
<param name=""first"">The first parameter.</param>
278+
<remarks></remarks>
279+
<example></example>
280+
<permission></permission>
281+
<exception></exception>
282+
</TestMethod>
283+
</TestClass>
284+
";
285+
resolver.XmlReferences.Add("NoneFilled.xml", contentNoneFilled);
286+
287+
project = base.ApplyCompilationOptions(project);
288+
project = project.WithCompilationOptions(project.CompilationOptions.WithXmlReferenceResolver(resolver));
289+
return project;
290+
}
291+
147292
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
148293
{
149294
yield return new SA1627DocumentationTextMustNotBeEmpty();

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1627DocumentationTextMustNotBeEmpty.cs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace StyleCop.Analyzers.DocumentationRules
66
using System;
77
using System.Collections.Immutable;
88
using System.Linq;
9+
using System.Xml.Linq;
910
using Helpers;
1011
using Microsoft.CodeAnalysis;
1112
using Microsoft.CodeAnalysis.CSharp;
@@ -93,11 +94,47 @@ private static void HandleXmlElement(SyntaxNodeAnalysisContext context)
9394

9495
private static void HandleXmlEmptyElement(SyntaxNodeAnalysisContext context)
9596
{
96-
var element = (XmlEmptyElementSyntax)context.Node;
97+
var elementSyntax = (XmlEmptyElementSyntax)context.Node;
98+
var elementName = elementSyntax.Name.ToString();
99+
var elementLocation = elementSyntax.GetLocation();
97100

98-
if (ElementsToCheck.Contains(element.Name.ToString()))
101+
if (string.Equals(elementName, XmlCommentHelper.IncludeXmlTag, StringComparison.Ordinal))
99102
{
100-
context.ReportDiagnostic(Diagnostic.Create(Descriptor, element.GetLocation(), element.Name.ToString()));
103+
HandleIncludedDocumentation(context, elementSyntax, elementLocation);
104+
return;
105+
}
106+
107+
if (ElementsToCheck.Contains(elementName))
108+
{
109+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, elementLocation, elementSyntax.Name.ToString()));
110+
}
111+
}
112+
113+
private static void HandleIncludedDocumentation(SyntaxNodeAnalysisContext context, XmlEmptyElementSyntax elementSyntax, Location elementLocation)
114+
{
115+
var memberDeclaration = elementSyntax.FirstAncestorOrSelf<MemberDeclarationSyntax>();
116+
if (memberDeclaration == null)
117+
{
118+
return;
119+
}
120+
121+
var declaration = context.SemanticModel.GetDeclaredSymbol(memberDeclaration, context.CancellationToken);
122+
var rawDocumentation = declaration?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: context.CancellationToken);
123+
var completeDocumentation = XElement.Parse(rawDocumentation, LoadOptions.None);
124+
if (completeDocumentation.Nodes().OfType<XElement>().Any(element => element.Name == XmlCommentHelper.InheritdocXmlTag))
125+
{
126+
// Ignore nodes with an <inheritdoc/> tag in the included XML.
127+
return;
128+
}
129+
130+
var emptyElements = completeDocumentation.Nodes()
131+
.OfType<XElement>()
132+
.Where(element => ElementsToCheck.Contains(element.Name.ToString()))
133+
.Where(x => XmlCommentHelper.IsConsideredEmpty(x));
134+
135+
foreach (var emptyElement in emptyElements)
136+
{
137+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, elementLocation, emptyElement.Name.ToString()));
101138
}
102139
}
103140
}

0 commit comments

Comments
 (0)