Skip to content

Commit 059f4c4

Browse files
peterisharwell
authored andcommitted
Code and tests for SA1627.
1 parent e7659d9 commit 059f4c4

4 files changed

Lines changed: 201 additions & 3 deletions

File tree

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Test.DocumentationRules
5+
{
6+
using System.Collections.Generic;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Analyzers.DocumentationRules;
10+
using Microsoft.CodeAnalysis.Diagnostics;
11+
using TestHelper;
12+
using Xunit;
13+
14+
/// <summary>
15+
/// This class contains unit tests for <see cref="SA1627DocumentationTextMustNotBeEmpty"/>.
16+
/// </summary>
17+
public class SA1627UnitTests : DiagnosticVerifier
18+
{
19+
public static IEnumerable<object[]> Elements
20+
{
21+
get
22+
{
23+
yield return new[] { "remarks" };
24+
yield return new[] { "example" };
25+
yield return new[] { "permission" };
26+
yield return new[] { "exception" };
27+
}
28+
}
29+
30+
/// <summary>
31+
/// Checks an element with a blank value gives an error.
32+
/// </summary>
33+
/// <param name="element">Element to check</param>
34+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
35+
[Theory]
36+
[MemberData(nameof(Elements))]
37+
public async Task TestMemberWithBlankElementAsync(string element)
38+
{
39+
var testCode = @"
40+
/// <summary>
41+
/// Foo
42+
/// </summary>
43+
public class ClassName
44+
{
45+
/// <summary>
46+
/// Foo
47+
/// </summary>
48+
///<param name=""foo"">Test</param>
49+
///<param name=""bar"">Test</param>
50+
/// <$$> </$$>
51+
public ClassName Method(string foo, string bar) { return null; }
52+
}";
53+
var expectedDiagnostic = this.CSharpDiagnostic().WithLocation(12, 9).WithArguments(element);
54+
await this.VerifyCSharpDiagnosticAsync(testCode.Replace("$$", element), expectedDiagnostic, CancellationToken.None).ConfigureAwait(false);
55+
}
56+
57+
/// <summary>
58+
/// Checks an element with a blank value gives an error.
59+
/// </summary>
60+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
61+
[Fact]
62+
public async Task TestMemberWithMultipleBlankElementsAsync()
63+
{
64+
var testCode = @"
65+
/// <summary>
66+
/// Foo
67+
/// </summary>
68+
public class ClassName
69+
{
70+
/// <summary>
71+
/// Foo
72+
/// </summary>
73+
///<param name=""foo"">Test</param>
74+
///<param name=""bar"">Test</param>
75+
/// <remarks>Foo bar</remarks>
76+
/// <example></example>
77+
/// <exception>
78+
///
79+
/// </exception>
80+
/// <permission>
81+
/// Multi line notes
82+
/// Multi line notes
83+
/// </permission>
84+
public ClassName Method(string foo, string bar) { return null; }
85+
}";
86+
var expectedDiagnostics = new[]
87+
{
88+
this.CSharpDiagnostic().WithLocation(13, 9).WithArguments("example"),
89+
this.CSharpDiagnostic().WithLocation(14, 9).WithArguments("exception")
90+
};
91+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostics, CancellationToken.None).ConfigureAwait(false);
92+
}
93+
94+
/// <summary>
95+
/// Checks an element with an empty element gives an error.
96+
/// </summary>
97+
/// <param name="element">Element to check</param>
98+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
99+
[Theory]
100+
[MemberData(nameof(Elements))]
101+
public async Task TestMemberWithEmptyElementAsync(string element)
102+
{
103+
var testCode = @"
104+
/// <summary>
105+
/// Foo
106+
/// </summary>
107+
public class ClassName
108+
{
109+
/// <summary>
110+
/// Foo
111+
/// </summary>
112+
///<param name=""foo"">Test</param>
113+
///<param name=""bar"">Test</param>
114+
/// <$$ />
115+
public ClassName Method(string foo, string bar) { return null; }
116+
}";
117+
var expectedDiagnostic = this.CSharpDiagnostic().WithLocation(12, 9).WithArguments(element);
118+
await this.VerifyCSharpDiagnosticAsync(testCode.Replace("$$", element), expectedDiagnostic, CancellationToken.None).ConfigureAwait(false);
119+
}
120+
121+
/// <summary>
122+
/// Checks an element with non blank text does not give an error.
123+
/// </summary>
124+
/// <param name="element">Element to check</param>
125+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
126+
[Theory]
127+
[MemberData(nameof(Elements))]
128+
public async Task TestMemberWithValidElementAsync(string element)
129+
{
130+
var testCode = @"
131+
/// <summary>
132+
/// Foo
133+
/// </summary>
134+
public class ClassName
135+
{
136+
/// <summary>
137+
/// Foo
138+
/// </summary>
139+
///<param name=""foo"">Test</param>
140+
///<param name=""bar"">Test</param>
141+
/// <$$>FooBar</$$>
142+
public ClassName Method(string foo, string bar) { return null; }
143+
}";
144+
await this.VerifyCSharpDiagnosticAsync(testCode.Replace("$$", element), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
145+
}
146+
147+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
148+
{
149+
yield return new SA1627DocumentationTextMustNotBeEmpty();
150+
}
151+
}
152+
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@
141141
<Compile Include="DocumentationRules\SA1621UnitTests.cs" />
142142
<Compile Include="DocumentationRules\SA1622UnitTests.cs" />
143143
<Compile Include="DocumentationRules\SA1626UnitTests.cs" />
144+
<Compile Include="DocumentationRules\SA1627UnitTests.cs" />
144145
<Compile Include="DocumentationRules\SA1628UnitTests.cs" />
145146
<Compile Include="DocumentationRules\SA1630UnitTests.cs" />
146147
<Compile Include="DocumentationRules\SA1631UnitTests.cs" />

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

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
namespace StyleCop.Analyzers.DocumentationRules
55
{
66
using System.Collections.Immutable;
7+
using System.Linq;
8+
using Helpers;
79
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Microsoft.CodeAnalysis.CSharp.Syntax;
812
using Microsoft.CodeAnalysis.Diagnostics;
913

1014
/// <summary>
@@ -24,7 +28,8 @@ namespace StyleCop.Analyzers.DocumentationRules
2428
/// /// &lt;summary&gt;
2529
/// /// Joins a first name and a last name together into a single string.
2630
/// /// &lt;/summary&gt;
27-
/// /// &lt;param name="firstName"&gt; &lt;/param&gt;
31+
/// /// &lt;remarks&gt;&lt;/remarks&gt;
32+
/// /// &lt;param name="firstName"&gt;Other part of name.&lt;/param&gt;
2833
/// /// &lt;param name="lastName"&gt;Part of the name.&lt;/param&gt;
2934
/// /// &lt;returns&gt;The joined names.&lt;/returns&gt;
3035
/// public string JoinNames(string firstName, string lastName)
@@ -42,7 +47,7 @@ internal class SA1627DocumentationTextMustNotBeEmpty : DiagnosticAnalyzer
4247
/// </summary>
4348
public const string DiagnosticId = "SA1627";
4449
private const string Title = "Documentation text must not be empty";
45-
private const string MessageFormat = "TODO: Message format";
50+
private const string MessageFormat = "The documentation text within the {0} tag must not be empty.";
4651
private const string Description = "The XML header documentation for a C# code element contains an empty tag.";
4752
private const string HelpLink = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1627.md";
4853

@@ -53,10 +58,46 @@ internal class SA1627DocumentationTextMustNotBeEmpty : DiagnosticAnalyzer
5358
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
5459
ImmutableArray.Create(Descriptor);
5560

61+
private static string[] elementsToCheck =
62+
{
63+
XmlCommentHelper.RemarksXmlTag,
64+
XmlCommentHelper.PermissionXmlTag,
65+
XmlCommentHelper.ExceptionXmlTag,
66+
XmlCommentHelper.ExampleXmlTag
67+
};
68+
5669
/// <inheritdoc/>
5770
public override void Initialize(AnalysisContext context)
5871
{
59-
// TODO: Implement analysis
72+
context.RegisterCompilationStartAction(HandleCompilationStart);
73+
}
74+
75+
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
76+
{
77+
context.RegisterSyntaxNodeActionHonorExclusions(HandleXmlElement, SyntaxKind.XmlElement);
78+
context.RegisterSyntaxNodeActionHonorExclusions(HandleXmlEmptyElement, SyntaxKind.XmlEmptyElement);
79+
}
80+
81+
private static void HandleXmlElement(SyntaxNodeAnalysisContext context)
82+
{
83+
XmlElementSyntax emptyElement = context.Node as XmlElementSyntax;
84+
85+
var name = emptyElement?.StartTag?.Name;
86+
87+
if (elementsToCheck.Contains(name.ToString()) && XmlCommentHelper.IsConsideredEmpty(emptyElement))
88+
{
89+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, emptyElement.GetLocation(), name.ToString()));
90+
}
91+
}
92+
93+
private static void HandleXmlEmptyElement(SyntaxNodeAnalysisContext context)
94+
{
95+
XmlEmptyElementSyntax emptyElement = context.Node as XmlEmptyElementSyntax;
96+
97+
if (elementsToCheck.Contains(emptyElement?.Name.ToString()))
98+
{
99+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, emptyElement.GetLocation(), emptyElement?.Name.ToString()));
100+
}
60101
}
61102
}
62103
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ internal static class XmlCommentHelper
2525
internal const string SeeXmlTag = "see";
2626
internal const string ParamXmlTag = "param";
2727
internal const string TypeParamXmlTag = "typeparam";
28+
internal const string RemarksXmlTag = "remarks";
29+
internal const string ExampleXmlTag = "example";
30+
internal const string PermissionXmlTag = "permission";
31+
internal const string ExceptionXmlTag = "exception";
2832
internal const string CrefArgumentName = "cref";
2933
internal const string NameArgumentName = "name";
3034

0 commit comments

Comments
 (0)