Skip to content

Commit 8e12234

Browse files
authored
Merge pull request #2694 from sharwell/sa1629-reporting
SA1629 reporting
2 parents 31da625 + bf6fec5 commit 8e12234

6 files changed

Lines changed: 223 additions & 47 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1608UnitTests.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,16 +111,19 @@ public async Task TestTypeWithoutContentDocumentationAsync(string typeName)
111111
await this.VerifyCSharpDiagnosticAsync(string.Format(testCode, typeName), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
112112
}
113113

114-
[Fact]
115-
public async Task TestClassWithDefaultDocumentationAsync()
114+
[Theory]
115+
[InlineData("class")]
116+
[InlineData("struct")]
117+
[InlineData("interface")]
118+
public async Task TestTypeWithDefaultDocumentationAsync(string typeName)
116119
{
117-
var testCode = @"
120+
var testCode = $@"
118121
/// <summary>
119122
/// Summary description for the ClassName class.
120123
/// </summary>
121-
public class ClassName
122-
{
123-
}";
124+
public {typeName} ClassName
125+
{{
126+
}}";
124127

125128
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(2, 5);
126129

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

Lines changed: 160 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33

44
namespace StyleCop.Analyzers.Test.DocumentationRules
55
{
6-
using System;
76
using System.Collections.Generic;
8-
using System.Linq;
97
using System.Threading;
108
using System.Threading.Tasks;
119
using Microsoft.CodeAnalysis;
@@ -27,7 +25,7 @@ public async Task TestDocumentationAsync()
2725
var testCode = @"
2826
/// <summary>
2927
/// Test class
30-
/// <summary>
28+
/// </summary>
3129
public class TestClass
3230
{
3331
/// <summary>
@@ -89,7 +87,7 @@ public int TestMethod2<T>(T arg1)
8987
var fixedTestCode = @"
9088
/// <summary>
9189
/// Test class.
92-
/// <summary>
90+
/// </summary>
9391
public class TestClass
9492
{
9593
/// <summary>
@@ -184,7 +182,7 @@ public async Task TestAugmentedInheritedDocumentationAsync()
184182
var testCode = @"
185183
/// <summary>
186184
/// Test interface.
187-
/// <summary>
185+
/// </summary>
188186
public interface ITest
189187
{
190188
/// <summary>Test method.</summary>
@@ -196,7 +194,7 @@ public interface ITest
196194
197195
/// <summary>
198196
/// Test class.
199-
/// <summary>
197+
/// </summary>
200198
public class TestClass : ITest
201199
{
202200
/// <inheritdoc/>
@@ -214,7 +212,7 @@ public int TestMethod<T>(T arg1)
214212
var fixedTestCode = @"
215213
/// <summary>
216214
/// Test interface.
217-
/// <summary>
215+
/// </summary>
218216
public interface ITest
219217
{
220218
/// <summary>Test method.</summary>
@@ -226,7 +224,7 @@ public interface ITest
226224
227225
/// <summary>
228226
/// Test class.
229-
/// <summary>
227+
/// </summary>
230228
public class TestClass : ITest
231229
{
232230
/// <inheritdoc/>
@@ -297,6 +295,160 @@ public class TestClass
297295
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
298296
}
299297

298+
[Fact]
299+
[WorkItem(2680, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2680")]
300+
public async Task TestReportingAfterEmptyElementAsync()
301+
{
302+
var testCode = @"
303+
/// <summary>
304+
/// Test interface <see cref=""ITest""/>
305+
/// </summary>
306+
public interface ITest
307+
{
308+
/// <summary>
309+
/// Test method <see cref=""Method""/>
310+
/// </summary>
311+
void Method();
312+
}
313+
";
314+
315+
var fixedTestCode = @"
316+
/// <summary>
317+
/// Test interface <see cref=""ITest""/>.
318+
/// </summary>
319+
public interface ITest
320+
{
321+
/// <summary>
322+
/// Test method <see cref=""Method""/>.
323+
/// </summary>
324+
void Method();
325+
}
326+
";
327+
328+
DiagnosticResult[] expected =
329+
{
330+
this.CSharpDiagnostic().WithLocation(3, 39),
331+
this.CSharpDiagnostic().WithLocation(8, 41),
332+
};
333+
334+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
335+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
336+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
337+
}
338+
339+
[Fact]
340+
[WorkItem(2680, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2680")]
341+
public async Task TestReportingAfterTwoEmptyElementsAsync()
342+
{
343+
var testCode = @"
344+
/// <summary>
345+
/// Test interface <see cref=""ITest""/> <see cref=""ITest""/>
346+
/// </summary>
347+
public interface ITest
348+
{
349+
/// <summary>
350+
/// Test method <see cref=""Method""/><see cref=""Method""/>
351+
/// </summary>
352+
void Method();
353+
}
354+
";
355+
356+
var fixedTestCode = @"
357+
/// <summary>
358+
/// Test interface <see cref=""ITest""/> <see cref=""ITest""/>.
359+
/// </summary>
360+
public interface ITest
361+
{
362+
/// <summary>
363+
/// Test method <see cref=""Method""/><see cref=""Method""/>.
364+
/// </summary>
365+
void Method();
366+
}
367+
";
368+
369+
DiagnosticResult[] expected =
370+
{
371+
this.CSharpDiagnostic().WithLocation(3, 59),
372+
this.CSharpDiagnostic().WithLocation(8, 61),
373+
};
374+
375+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
376+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
377+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
378+
}
379+
380+
[Fact]
381+
[WorkItem(2680, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2680")]
382+
public async Task TestReportingAfterEmptyElementTwoSentencesAsync()
383+
{
384+
var testCode = @"
385+
/// <summary>
386+
/// Test interface. <see cref=""ITest""/>
387+
/// </summary>
388+
public interface ITest
389+
{
390+
/// <summary>
391+
/// Test method. <see cref=""Method""/><see cref=""Method""/>
392+
/// </summary>
393+
void Method();
394+
}
395+
";
396+
397+
var fixedTestCode = @"
398+
/// <summary>
399+
/// Test interface. <see cref=""ITest""/>.
400+
/// </summary>
401+
public interface ITest
402+
{
403+
/// <summary>
404+
/// Test method. <see cref=""Method""/><see cref=""Method""/>.
405+
/// </summary>
406+
void Method();
407+
}
408+
";
409+
410+
DiagnosticResult[] expected =
411+
{
412+
this.CSharpDiagnostic().WithLocation(3, 40),
413+
this.CSharpDiagnostic().WithLocation(8, 62),
414+
};
415+
416+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
417+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
418+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
419+
}
420+
421+
[Fact]
422+
[WorkItem(2679, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2679")]
423+
public async Task TestElementsThatDoNotRequirePeriodsAsync()
424+
{
425+
var testCode = @"
426+
/// <summary>
427+
/// Test interface <see cref=""ITest"">a see element</see>
428+
/// </summary>
429+
/// <seealso href=""https://docs.microsoft.com/en-us/dotnet/framework/wpf/index"">Windows Presentation Foundation</seealso>
430+
public interface ITest
431+
{
432+
}
433+
";
434+
435+
var fixedTestCode = @"
436+
/// <summary>
437+
/// Test interface <see cref=""ITest"">a see element</see>.
438+
/// </summary>
439+
/// <seealso href=""https://docs.microsoft.com/en-us/dotnet/framework/wpf/index"">Windows Presentation Foundation</seealso>
440+
public interface ITest
441+
{
442+
}
443+
";
444+
445+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(3, 57);
446+
447+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
448+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
449+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
450+
}
451+
300452
protected override Project ApplyCompilationOptions(Project project)
301453
{
302454
var resolver = new TestXmlReferenceResolver();

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/ElementDocumentationBase.cs

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ internal abstract class ElementDocumentationBase : DiagnosticAnalyzer
2828
private readonly Action<SyntaxNodeAnalysisContext, StyleCopSettings> indexerDeclarationAction;
2929
private readonly Action<SyntaxNodeAnalysisContext, StyleCopSettings> operatorDeclarationAction;
3030
private readonly Action<SyntaxNodeAnalysisContext, StyleCopSettings> conversionOperatorDeclarationAction;
31-
private readonly Action<SyntaxNodeAnalysisContext, StyleCopSettings> classDeclarationAction;
32-
private readonly Action<SyntaxNodeAnalysisContext, StyleCopSettings> structDeclarationAction;
33-
private readonly Action<SyntaxNodeAnalysisContext, StyleCopSettings> enumDeclarationAction;
31+
private readonly Action<SyntaxNodeAnalysisContext, StyleCopSettings> baseTypeDeclarationAction;
3432
private readonly Action<SyntaxNodeAnalysisContext, StyleCopSettings> fieldDeclarationAction;
3533
private readonly Action<SyntaxNodeAnalysisContext, StyleCopSettings> propertyDeclarationAction;
3634

@@ -45,9 +43,7 @@ protected ElementDocumentationBase(bool inheritDocSuppressesWarnings, string mat
4543
this.indexerDeclarationAction = this.HandleIndexerDeclaration;
4644
this.operatorDeclarationAction = this.HandleOperatorDeclaration;
4745
this.conversionOperatorDeclarationAction = this.HandleConversionOperatorDeclaration;
48-
this.classDeclarationAction = this.HandleClassDeclaration;
49-
this.structDeclarationAction = this.HandleStructDeclaration;
50-
this.enumDeclarationAction = this.HandleEnumDeclaration;
46+
this.baseTypeDeclarationAction = this.HandleBaseTypeDeclaration;
5147
this.fieldDeclarationAction = this.HandleFieldDeclaration;
5248
this.propertyDeclarationAction = this.HandlePropertyDeclaration;
5349
}
@@ -64,9 +60,7 @@ public override void Initialize(AnalysisContext context)
6460
context.RegisterSyntaxNodeAction(this.indexerDeclarationAction, SyntaxKind.IndexerDeclaration);
6561
context.RegisterSyntaxNodeAction(this.operatorDeclarationAction, SyntaxKind.OperatorDeclaration);
6662
context.RegisterSyntaxNodeAction(this.conversionOperatorDeclarationAction, SyntaxKind.ConversionOperatorDeclaration);
67-
context.RegisterSyntaxNodeAction(this.classDeclarationAction, SyntaxKind.ClassDeclaration);
68-
context.RegisterSyntaxNodeAction(this.structDeclarationAction, SyntaxKind.StructDeclaration);
69-
context.RegisterSyntaxNodeAction(this.enumDeclarationAction, SyntaxKind.EnumDeclaration);
63+
context.RegisterSyntaxNodeAction(this.baseTypeDeclarationAction, SyntaxKinds.BaseTypeDeclaration);
7064
context.RegisterSyntaxNodeAction(this.fieldDeclarationAction, SyntaxKind.FieldDeclaration);
7165
context.RegisterSyntaxNodeAction(this.propertyDeclarationAction, SyntaxKind.PropertyDeclaration);
7266
}
@@ -175,29 +169,9 @@ private void HandleConversionOperatorDeclaration(SyntaxNodeAnalysisContext conte
175169
this.HandleDeclaration(context, needsComment, node, node.GetLocation());
176170
}
177171

178-
private void HandleClassDeclaration(SyntaxNodeAnalysisContext context, StyleCopSettings settings)
172+
private void HandleBaseTypeDeclaration(SyntaxNodeAnalysisContext context, StyleCopSettings settings)
179173
{
180-
var node = (ClassDeclarationSyntax)context.Node;
181-
182-
Accessibility declaredAccessibility = node.GetDeclaredAccessibility(context.SemanticModel, context.CancellationToken);
183-
Accessibility effectiveAccessibility = node.GetEffectiveAccessibility(context.SemanticModel, context.CancellationToken);
184-
bool needsComment = SA1600ElementsMustBeDocumented.NeedsComment(settings.DocumentationRules, node.Kind(), node.Parent.Kind(), declaredAccessibility, effectiveAccessibility);
185-
this.HandleDeclaration(context, needsComment, node, node.Identifier.GetLocation());
186-
}
187-
188-
private void HandleStructDeclaration(SyntaxNodeAnalysisContext context, StyleCopSettings settings)
189-
{
190-
var node = (StructDeclarationSyntax)context.Node;
191-
192-
Accessibility declaredAccessibility = node.GetDeclaredAccessibility(context.SemanticModel, context.CancellationToken);
193-
Accessibility effectiveAccessibility = node.GetEffectiveAccessibility(context.SemanticModel, context.CancellationToken);
194-
bool needsComment = SA1600ElementsMustBeDocumented.NeedsComment(settings.DocumentationRules, node.Kind(), node.Parent.Kind(), declaredAccessibility, effectiveAccessibility);
195-
this.HandleDeclaration(context, needsComment, node, node.Identifier.GetLocation());
196-
}
197-
198-
private void HandleEnumDeclaration(SyntaxNodeAnalysisContext context, StyleCopSettings settings)
199-
{
200-
var node = (EnumDeclarationSyntax)context.Node;
174+
var node = (BaseTypeDeclarationSyntax)context.Node;
201175

202176
Accessibility declaredAccessibility = node.GetDeclaredAccessibility(context.SemanticModel, context.CancellationToken);
203177
Accessibility effectiveAccessibility = node.GetEffectiveAccessibility(context.SemanticModel, context.CancellationToken);

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace StyleCop.Analyzers.DocumentationRules
1212
using Microsoft.CodeAnalysis.CSharp.Syntax;
1313
using Microsoft.CodeAnalysis.Diagnostics;
1414
using Microsoft.CodeAnalysis.Text;
15+
using StyleCop.Analyzers.Helpers;
1516

1617
/// <summary>
1718
/// A section of the XML header documentation for a C# element does not end with a period (also known as a full
@@ -77,6 +78,11 @@ protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, bool
7778
{
7879
foreach (var xmlElement in syntaxList.OfType<XmlElementSyntax>())
7980
{
81+
if (xmlElement.StartTag?.Name?.LocalName.ValueText == XmlCommentHelper.SeeAlsoXmlTag)
82+
{
83+
continue;
84+
}
85+
8086
var elementDone = false;
8187
for (var i = xmlElement.Content.Count - 1; !elementDone && (i >= 0); i--)
8288
{
@@ -99,6 +105,13 @@ protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, bool
99105
}
100106
}
101107
}
108+
else if (xmlElement.Content[i].IsInlineElement())
109+
{
110+
// Treat empty XML elements as a "word not ending with a period"
111+
var location = Location.Create(xmlElement.SyntaxTree, new TextSpan(xmlElement.Content[i].Span.End, 1));
112+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, location));
113+
elementDone = true;
114+
}
102115
}
103116
}
104117
}

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,13 @@ internal static class XmlCommentHelper
2121
internal const string InheritdocXmlTag = "inheritdoc";
2222
internal const string ReturnsXmlTag = "returns";
2323
internal const string ValueXmlTag = "value";
24+
internal const string CXmlTag = "c";
2425
internal const string SeeXmlTag = "see";
26+
internal const string SeeAlsoXmlTag = "seealso";
2527
internal const string ParamXmlTag = "param";
28+
internal const string ParamRefXmlTag = "paramref";
2629
internal const string TypeParamXmlTag = "typeparam";
30+
internal const string TypeParamRefXmlTag = "typeparamref";
2731
internal const string RemarksXmlTag = "remarks";
2832
internal const string ExampleXmlTag = "example";
2933
internal const string PermissionXmlTag = "permission";
@@ -264,5 +268,35 @@ internal static T GetFirstAttributeOrDefault<T>(XmlNodeSyntax nodeSyntax)
264268

265269
return null;
266270
}
271+
272+
internal static bool IsInlineElement(this XmlNodeSyntax nodeSyntax)
273+
{
274+
if (nodeSyntax is XmlEmptyElementSyntax emptyElementSyntax)
275+
{
276+
return IsInlineElement(emptyElementSyntax.Name?.LocalName.ValueText);
277+
}
278+
279+
if (nodeSyntax is XmlElementSyntax elementSyntax)
280+
{
281+
return IsInlineElement(elementSyntax.StartTag?.Name?.LocalName.ValueText);
282+
}
283+
284+
return false;
285+
}
286+
287+
private static bool IsInlineElement(string localName)
288+
{
289+
switch (localName)
290+
{
291+
case CXmlTag:
292+
case ParamRefXmlTag:
293+
case SeeXmlTag:
294+
case TypeParamRefXmlTag:
295+
return true;
296+
297+
default:
298+
return false;
299+
}
300+
}
267301
}
268302
}

0 commit comments

Comments
 (0)