Skip to content

Commit eed3e6b

Browse files
committed
Updated SA1617
1 parent 2c4e1ca commit eed3e6b

3 files changed

Lines changed: 143 additions & 7 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/SA1617CodeFixProvider.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
3939
{
4040
foreach (Diagnostic diagnostic in context.Diagnostics)
4141
{
42-
context.RegisterCodeFix(
43-
CodeAction.Create(
44-
DocumentationResources.SA1617CodeFix,
45-
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
46-
nameof(SA1617CodeFixProvider)),
47-
diagnostic);
42+
if (!diagnostic.Properties.ContainsKey(SA1617VoidReturnValueMustNotBeDocumented.NoCodeFixKey))
43+
{
44+
context.RegisterCodeFix(
45+
CodeAction.Create(
46+
DocumentationResources.SA1617CodeFix,
47+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
48+
nameof(SA1617CodeFixProvider)),
49+
diagnostic);
50+
}
4851
}
4952

5053
return SpecializedTasks.CompletedTask;

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1617UnitTests.cs

Lines changed: 101 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.CodeFixes;
1113
using Microsoft.CodeAnalysis.Diagnostics;
1214
using TestHelper;
@@ -262,6 +264,105 @@ public void Method() { }
262264
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
263265
}
264266

267+
/// <summary>
268+
/// Verifies that included method documentation without a returns tag will be accepted.
269+
/// </summary>
270+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
271+
[Fact]
272+
public async Task TestMethodWithValidIncludeAsync()
273+
{
274+
var testCode = @"
275+
public class ClassName
276+
{
277+
/// <include file='MethodWithoutReturns.xml' path='/ClassName/Method/*'/>
278+
public void Method() { }
279+
}";
280+
281+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
282+
}
283+
284+
/// <summary>
285+
/// Verifies that included method documentation with a returns tag will be flagged.
286+
/// </summary>
287+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
288+
[Fact]
289+
public async Task TestMethodWithReturnsInIncludeAsync()
290+
{
291+
var testCode = @"
292+
public class ClassName
293+
{
294+
/// <include file='MethodWithReturns.xml' path='/ClassName/Method/*'/>
295+
public void Method() { }
296+
}";
297+
298+
var expected = this.CSharpDiagnostic().WithLocation(4, 9);
299+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
300+
var offeredFixes = await this.GetOfferedCSharpFixesAsync(testCode).ConfigureAwait(false);
301+
Assert.Empty(offeredFixes);
302+
}
303+
304+
/// <summary>
305+
/// Verifies that included method documentation containing &gt;inheritdoc/&lt; will be accepted.
306+
/// </summary>
307+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
308+
[Fact]
309+
public async Task TestMethodWithInheritdocInIncludeAsync()
310+
{
311+
var testCode = @"
312+
public interface ITestInterface
313+
{
314+
/// <summary>
315+
/// Foo bar.
316+
/// </summary>
317+
void Method();
318+
}
319+
320+
public class ClassName : ITestInterface
321+
{
322+
/// <include file='MethodWithInheritdoc.xml' path='/ClassName/Method/*'/>
323+
public void Method() { }
324+
}";
325+
326+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
327+
}
328+
329+
protected override Project ApplyCompilationOptions(Project project)
330+
{
331+
var resolver = new TestXmlReferenceResolver();
332+
333+
string contentWithoutReturns = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
334+
<ClassName>
335+
<Method>
336+
<summary>Foo</summary>
337+
</Method>
338+
</ClassName>
339+
";
340+
resolver.XmlReferences.Add("MethodWithoutReturns.xml", contentWithoutReturns);
341+
342+
string contentWithReturns = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
343+
<ClassName>
344+
<Method>
345+
<summary>Foo</summary>
346+
<returns>Bar</returns>
347+
</Method>
348+
</ClassName>
349+
";
350+
resolver.XmlReferences.Add("MethodWithReturns.xml", contentWithReturns);
351+
352+
string contentWithInheritdocValue = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
353+
<ClassName>
354+
<Method>
355+
<inheritdoc/>
356+
</Method>
357+
</ClassName>
358+
";
359+
resolver.XmlReferences.Add("MethodWithInheritdoc.xml", contentWithInheritdocValue);
360+
361+
project = base.ApplyCompilationOptions(project);
362+
project = project.WithCompilationOptions(project.CompilationOptions.WithXmlReferenceResolver(resolver));
363+
return project;
364+
}
365+
265366
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
266367
{
267368
yield return new SA1617VoidReturnValueMustNotBeDocumented();

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1617VoidReturnValueMustNotBeDocumented.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ namespace StyleCop.Analyzers.DocumentationRules
55
{
66
using System;
77
using System.Collections.Immutable;
8+
using System.Linq;
9+
using System.Xml.Linq;
810
using Microsoft.CodeAnalysis;
911
using Microsoft.CodeAnalysis.CSharp;
1012
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -32,6 +34,12 @@ internal class SA1617VoidReturnValueMustNotBeDocumented : DiagnosticAnalyzer
3234
/// The ID for diagnostics produced by the <see cref="SA1617VoidReturnValueMustNotBeDocumented"/> analyzer.
3335
/// </summary>
3436
public const string DiagnosticId = "SA1617";
37+
38+
/// <summary>
39+
/// The key used for signalling that no codefix should be offered.
40+
/// </summary>
41+
internal const string NoCodeFixKey = "NoCodeFix";
42+
3543
private const string Title = "Void return value must not be documented";
3644
private const string MessageFormat = "Void return value must not be documented";
3745
private const string Description = "A C# code element does not contain a return value, or returns void, but the documentation header for the element contains a <returns> tag.";
@@ -82,8 +90,32 @@ private static void HandleMember(SyntaxNodeAnalysisContext context, TypeSyntax r
8290
{
8391
// Check if the return value is documented
8492
var returnsElement = documentation.Content.GetFirstXmlElement(XmlCommentHelper.ReturnsXmlTag);
93+
if (returnsElement == null)
94+
{
95+
var includeElement = documentation.Content.GetFirstXmlElement(XmlCommentHelper.IncludeXmlTag);
96+
if (includeElement != null)
97+
{
98+
string rawDocumentation;
99+
var declaration = context.SemanticModel.GetDeclaredSymbol(context.Node, context.CancellationToken);
100+
rawDocumentation = declaration?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: context.CancellationToken);
101+
var completeDocumentation = XElement.Parse(rawDocumentation, LoadOptions.None);
102+
if (completeDocumentation.Nodes().OfType<XElement>().Any(element => element.Name == XmlCommentHelper.InheritdocXmlTag))
103+
{
104+
// Ignore nodes with an <inheritdoc/> tag in the included XML.
105+
return;
106+
}
107+
108+
var includedReturnsElement = completeDocumentation.Nodes().OfType<XElement>().FirstOrDefault(element => element.Name == XmlCommentHelper.ReturnsXmlTag);
109+
if (includedReturnsElement != null)
110+
{
111+
var properties = ImmutableDictionary.Create<string, string>()
112+
.Add(NoCodeFixKey, string.Empty);
85113

86-
if (returnsElement != null)
114+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, includeElement.GetLocation(), properties));
115+
}
116+
}
117+
}
118+
else
87119
{
88120
context.ReportDiagnostic(Diagnostic.Create(Descriptor, returnsElement.GetLocation()));
89121
}

0 commit comments

Comments
 (0)