Skip to content

Commit 1319e88

Browse files
committed
Updated SA1618
1 parent 873dd2b commit 1319e88

2 files changed

Lines changed: 217 additions & 7 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1618UnitTests.cs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ namespace StyleCop.Analyzers.Test.DocumentationRules
77
using System.Threading;
88
using System.Threading.Tasks;
99
using Analyzers.DocumentationRules;
10+
using Helpers;
11+
using Microsoft.CodeAnalysis;
1012
using Microsoft.CodeAnalysis.Diagnostics;
1113
using TestHelper;
1214
using Xunit;
@@ -277,6 +279,182 @@ public async Task TestPartialTypesWithMissingDocumentationAsync(string p)
277279
await this.VerifyCSharpDiagnosticAsync(testCode.Replace("##", p), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
278280
}
279281

282+
/// <summary>
283+
/// Verifies that a generic type with included documentation will work.
284+
/// </summary>
285+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
286+
[Fact]
287+
public async Task TestGenericTypeWithIncludedDocumentationAsync()
288+
{
289+
var testCode = @"
290+
/// <include file='ClassWithTypeparamDoc.xml' path='/TestClass/*'/>
291+
public class TestClass<T>
292+
{
293+
}
294+
";
295+
296+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
297+
}
298+
299+
/// <summary>
300+
/// Verifies that a generic method with included documentation will work.
301+
/// </summary>
302+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
303+
[Fact]
304+
public async Task TestGenericMethodWithIncludedDocumentationAsync()
305+
{
306+
var testCode = @"
307+
/// <summary>
308+
/// Test class
309+
/// </summary>
310+
public class TestClass
311+
{
312+
/// <include file='MethodWithTypeparamDoc.xml' path='/TestClass/TestMethod/*'/>
313+
public void TestMethod<T>(T param1) { }
314+
}
315+
";
316+
317+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
318+
}
319+
320+
321+
/// <summary>
322+
/// Verifies that a generic type without a typeparam in included documentation will flag.
323+
/// </summary>
324+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
325+
[Fact]
326+
public async Task TestGenericTypeWithoutTypeparamInIncludedDocumentationAsync()
327+
{
328+
var testCode = @"
329+
/// <include file='ClassWithoutTypeparamDoc.xml' path='/TestClass/*'/>
330+
public class TestClass<T>
331+
{
332+
}
333+
";
334+
335+
var expected = this.CSharpDiagnostic().WithLocation(3, 24).WithArguments("T");
336+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
337+
}
338+
339+
/// <summary>
340+
/// Verifies that a generic method without a typeparam included documentation will flag.
341+
/// </summary>
342+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
343+
[Fact]
344+
public async Task TestGenericMethodWithoutTypeparamInIncludedDocumentationAsync()
345+
{
346+
var testCode = @"
347+
/// <summary>
348+
/// Test class
349+
/// </summary>
350+
public class TestClass
351+
{
352+
/// <include file='MethodWithoutTypeparamDoc.xml' path='/TestClass/TestMethod/*'/>
353+
public void TestMethod<T>(T param1) { }
354+
}
355+
";
356+
357+
var expected = this.CSharpDiagnostic().WithLocation(8, 26).WithArguments("T");
358+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
359+
}
360+
361+
/// <summary>
362+
/// Verifies that a generic type with &lt;inheritdoc&gt; in included documentation will work.
363+
/// </summary>
364+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
365+
[Fact]
366+
public async Task TestGenericTypeWithInheritdocInIncludedDocumentationAsync()
367+
{
368+
var testCode = @"
369+
/// <include file='ClassWithIneheritdoc.xml' path='/TestClass/*'/>
370+
public class TestClass<T>
371+
{
372+
}
373+
";
374+
375+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
376+
}
377+
378+
/// <summary>
379+
/// Verifies that a generic method with &lt;inheritdoc&gt; in included documentation will work.
380+
/// </summary>
381+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
382+
[Fact]
383+
public async Task TestGenericMethodWithInheritdocInIncludedDocumentationAsync()
384+
{
385+
var testCode = @"
386+
/// <summary>
387+
/// Test class
388+
/// </summary>
389+
public class TestClass
390+
{
391+
/// <include file='MethodWithInheritdoc.xml' path='/TestClass/TestMethod/*'/>
392+
public void TestMethod<T>(T param1) { }
393+
}
394+
";
395+
396+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
397+
}
398+
399+
protected override Project ApplyCompilationOptions(Project project)
400+
{
401+
var resolver = new TestXmlReferenceResolver();
402+
403+
string contentClassWithTypeparamDoc = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
404+
<TestClass>
405+
<summary>Test class</summary>
406+
<typeparam name=""T"">Param 1</typeparam>
407+
</TestClass>
408+
";
409+
resolver.XmlReferences.Add("ClassWithTypeparamDoc.xml", contentClassWithTypeparamDoc);
410+
411+
string contentMethodWithTypeparamDoc = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
412+
<TestClass>
413+
<TestMethod>
414+
<summary>Test class</summary>
415+
<typeparam name=""T"">Param 1</typeparam>
416+
</TestMethod>
417+
</TestClass>
418+
";
419+
resolver.XmlReferences.Add("MethodWithTypeparamDoc.xml", contentMethodWithTypeparamDoc);
420+
421+
string contentClassWithoutTypeparamDoc = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
422+
<TestClass>
423+
<summary>Test class</summary>
424+
</TestClass>
425+
";
426+
resolver.XmlReferences.Add("ClassWithoutTypeparamDoc.xml", contentClassWithoutTypeparamDoc);
427+
428+
string contentMethodWithoutTypeparamDoc = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
429+
<TestClass>
430+
<TestMethod>
431+
<summary>Test class</summary>
432+
</TestMethod>
433+
</TestClass>
434+
";
435+
resolver.XmlReferences.Add("MethodWithoutTypeparamDoc.xml", contentMethodWithoutTypeparamDoc);
436+
437+
string contentClassInheritdoc = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
438+
<TestClass>
439+
<inheritdoc/>
440+
</TestClass>
441+
";
442+
resolver.XmlReferences.Add("ClassWithIneheritdoc.xml", contentClassInheritdoc);
443+
444+
string contentMethodWithInheritdoc = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
445+
<TestClass>
446+
<TestMethod>
447+
<inheritdoc/>
448+
</TestMethod>
449+
</TestClass>
450+
";
451+
resolver.XmlReferences.Add("MethodWithInheritdoc.xml", contentMethodWithInheritdoc);
452+
453+
project = base.ApplyCompilationOptions(project);
454+
project = project.WithCompilationOptions(project.CompilationOptions.WithXmlReferenceResolver(resolver));
455+
return project;
456+
}
457+
280458
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
281459
{
282460
yield return new SA1618GenericTypeParametersMustBeDocumented();

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1618GenericTypeParametersMustBeDocumented.cs

Lines changed: 39 additions & 7 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 Microsoft.CodeAnalysis;
1011
using Microsoft.CodeAnalysis.CSharp;
1112
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -108,16 +109,47 @@ private static void HandleMemberDeclaration(SyntaxNodeAnalysisContext context, S
108109
return;
109110
}
110111

111-
var xmlParameterNames = documentation.Content.GetXmlElements(XmlCommentHelper.TypeParamXmlTag)
112-
.Select(XmlCommentHelper.GetFirstAttributeOrDefault<XmlNameAttributeSyntax>)
113-
.Where(x => x != null)
114-
.ToImmutableArray();
112+
// Check if the return value is documented
113+
var includeElement = documentation.Content.GetFirstXmlElement(XmlCommentHelper.IncludeXmlTag);
114+
if (includeElement != null)
115+
{
116+
string rawDocumentation;
117+
var declaration = context.SemanticModel.GetDeclaredSymbol(context.Node, context.CancellationToken);
118+
rawDocumentation = declaration?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: context.CancellationToken);
119+
var completeDocumentation = XElement.Parse(rawDocumentation, LoadOptions.None);
120+
if (completeDocumentation.Nodes().OfType<XElement>().Any(element => element.Name == XmlCommentHelper.InheritdocXmlTag))
121+
{
122+
// Ignore nodes with an <inheritdoc/> tag in the included XML.
123+
return;
124+
}
125+
126+
var typeParameterAttributes = completeDocumentation.Nodes()
127+
.OfType<XElement>()
128+
.Where(element => element.Name == XmlCommentHelper.TypeParamXmlTag)
129+
.Select(element => element.Attribute(XmlCommentHelper.NameArgumentName))
130+
.Where(x => x != null);
115131

116-
foreach (var parameter in typeParameterList.Parameters)
132+
foreach (var parameter in typeParameterList.Parameters)
133+
{
134+
if (!typeParameterAttributes.Any(x => x.Value == parameter.Identifier.ValueText))
135+
{
136+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, parameter.Identifier.GetLocation(), parameter.Identifier.ValueText));
137+
}
138+
}
139+
}
140+
else
117141
{
118-
if (!xmlParameterNames.Any(x => x.Identifier.Identifier.ValueText == parameter.Identifier.ValueText))
142+
var xmlParameterNames = documentation.Content.GetXmlElements(XmlCommentHelper.TypeParamXmlTag)
143+
.Select(XmlCommentHelper.GetFirstAttributeOrDefault<XmlNameAttributeSyntax>)
144+
.Where(x => x != null)
145+
.ToImmutableArray();
146+
147+
foreach (var parameter in typeParameterList.Parameters)
119148
{
120-
context.ReportDiagnostic(Diagnostic.Create(Descriptor, parameter.Identifier.GetLocation(), parameter.Identifier.ValueText));
149+
if (!xmlParameterNames.Any(x => x.Identifier.Identifier.ValueText == parameter.Identifier.ValueText))
150+
{
151+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, parameter.Identifier.GetLocation(), parameter.Identifier.ValueText));
152+
}
121153
}
122154
}
123155
}

0 commit comments

Comments
 (0)