Skip to content

Commit c18b86b

Browse files
committed
Update SA1608 to handle included documentation (fixes #1900)
1 parent 7e69f3e commit c18b86b

2 files changed

Lines changed: 105 additions & 23 deletions

File tree

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

Lines changed: 74 additions & 1 deletion
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;
@@ -142,7 +144,78 @@ public class ClassName
142144
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
143145
}
144146

145-
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
147+
[Fact]
148+
public async Task TestClassWithIncludedEmptyDocumentationAsync()
149+
{
150+
var testCode = @"
151+
/// <include file='ClassWithoutSummary.xml' path='/ClassName/*' />
152+
public class ClassName
153+
{
154+
}";
155+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
156+
}
157+
158+
[Fact]
159+
public async Task TestClassWithIncludedSummaryDocumentationAsync()
160+
{
161+
var testCode = @"
162+
/// <include file='ClassWithSummary.xml' path='/ClassName/*' />
163+
public class ClassName
164+
{
165+
}";
166+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
167+
}
168+
169+
[Fact]
170+
public async Task TestClassWithIncludedDefaultSummaryDocumentationAsync()
171+
{
172+
var testCode = @"
173+
/// <include file='ClassWithDefaultSummary.xml' path='/ClassName/*' />
174+
public class ClassName
175+
{
176+
}";
177+
178+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(3, 14);
179+
180+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
181+
}
182+
183+
/// <inheritdoc/>
184+
protected override Project CreateProject(string[] sources, string language = "C#", string[] filenames = null)
185+
{
186+
var resolver = new TestXmlReferenceResolver();
187+
188+
string contentWithoutSummary = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
189+
<ClassName>
190+
</ClassName>
191+
";
192+
resolver.XmlReferences.Add("ClassWithoutSummary.xml", contentWithoutSummary);
193+
194+
string contentWithSummary = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
195+
<ClassName>
196+
<summary>
197+
Foo
198+
</summary>
199+
</ClassName>
200+
";
201+
resolver.XmlReferences.Add("ClassWithSummary.xml", contentWithSummary);
202+
203+
string contentWithDefaultSummary = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
204+
<ClassName>
205+
<summary>
206+
Summary description for the ClassName class.
207+
</summary>
208+
</ClassName>
209+
";
210+
resolver.XmlReferences.Add("ClassWithDefaultSummary.xml", contentWithDefaultSummary);
211+
212+
Project project = base.CreateProject(sources, language, filenames);
213+
project = project.WithCompilationOptions(project.CompilationOptions.WithXmlReferenceResolver(resolver));
214+
return project;
215+
}
216+
217+
/// <inheritdoc/>
218+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
146219
{
147220
yield return new SA1608ElementDocumentationMustNotHaveDefaultSummary();
148221
}

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1608ElementDocumentationMustNotHaveDefaultSummary.cs

Lines changed: 31 additions & 22 deletions
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 Helpers;
911
using Microsoft.CodeAnalysis;
1012
using Microsoft.CodeAnalysis.CSharp;
@@ -30,7 +32,7 @@ namespace StyleCop.Analyzers.DocumentationRules
3032
/// default documentation text generated by Visual Studio.</para>
3133
/// </remarks>
3234
[DiagnosticAnalyzer(LanguageNames.CSharp)]
33-
internal class SA1608ElementDocumentationMustNotHaveDefaultSummary : DiagnosticAnalyzer
35+
internal class SA1608ElementDocumentationMustNotHaveDefaultSummary : ElementDocumentationSummaryBase
3436
{
3537
/// <summary>
3638
/// The ID for diagnostics produced by the <see cref="SA1608ElementDocumentationMustNotHaveDefaultSummary"/>
@@ -47,50 +49,57 @@ internal class SA1608ElementDocumentationMustNotHaveDefaultSummary : DiagnosticA
4749
private static readonly DiagnosticDescriptor Descriptor =
4850
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.DocumentationRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
4951

50-
private static readonly Action<CompilationStartAnalysisContext> CompilationStartAction = HandleCompilationStart;
51-
private static readonly Action<SyntaxNodeAnalysisContext> DocumentationAction = HandleDocumentation;
52+
private static readonly ImmutableArray<SyntaxKind> DocumentationCommentKinds =
53+
ImmutableArray.Create(SyntaxKind.SingleLineDocumentationCommentTrivia, SyntaxKind.MultiLineDocumentationCommentTrivia);
5254

5355
/// <inheritdoc/>
5456
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
5557
ImmutableArray.Create(Descriptor);
5658

5759
/// <inheritdoc/>
58-
public override void Initialize(AnalysisContext context)
60+
protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, XElement completeDocumentation, params Location[] diagnosticLocations)
5961
{
60-
context.RegisterCompilationStartAction(CompilationStartAction);
61-
}
62-
63-
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
62+
if (completeDocumentation != null)
6463
{
65-
context.RegisterSyntaxNodeActionHonorExclusions(DocumentationAction, SyntaxKinds.DocumentationComment);
66-
}
67-
68-
private static void HandleDocumentation(SyntaxNodeAnalysisContext context)
64+
// We are working with an <include> element
65+
var includedSummaryElement = completeDocumentation.Nodes().OfType<XElement>().FirstOrDefault(element => element.Name == XmlCommentHelper.SummaryXmlTag);
66+
if (includedSummaryElement != null)
6967
{
70-
var documentationTrivia = context.Node as DocumentationCommentTriviaSyntax;
68+
string text = includedSummaryElement.Value;
7169

72-
if (documentationTrivia != null)
70+
if (IsDefaultText(text))
7371
{
74-
var summaryElement = documentationTrivia.Content.GetFirstXmlElement(XmlCommentHelper.SummaryXmlTag) as XmlElementSyntax;
75-
76-
if (summaryElement != null)
77-
{
78-
var textElement = summaryElement.Content.FirstOrDefault() as XmlTextSyntax;
72+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, diagnosticLocations.First()));
73+
return;
74+
}
75+
}
76+
}
7977

78+
var summaryElement = syntax as XmlElementSyntax;
79+
var textElement = summaryElement?.Content.FirstOrDefault() as XmlTextSyntax;
8080
if (textElement != null)
8181
{
8282
string text = XmlCommentHelper.GetText(textElement, true);
8383

84-
if (!string.IsNullOrEmpty(text))
85-
{
86-
if (text.TrimStart().StartsWith(DefaultText, StringComparison.Ordinal))
84+
if (IsDefaultText(text))
8785
{
8886
context.ReportDiagnostic(Diagnostic.Create(Descriptor, summaryElement.GetLocation()));
87+
return;
8988
}
9089
}
9190
}
91+
92+
private static bool IsDefaultText(string text)
93+
{
94+
if (!string.IsNullOrEmpty(text))
95+
{
96+
if (text.TrimStart().StartsWith(DefaultText, StringComparison.Ordinal))
97+
{
98+
return true;
9299
}
93100
}
101+
102+
return false;
94103
}
95104
}
96105
}

0 commit comments

Comments
 (0)