Skip to content

Commit 190f3ff

Browse files
committed
Updated SA1648
1 parent 45449ed commit 190f3ff

2 files changed

Lines changed: 210 additions & 14 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1648UnitTests.cs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ namespace StyleCop.Analyzers.Test.DocumentationRules
66
using System.Collections.Generic;
77
using System.Threading;
88
using System.Threading.Tasks;
9+
using Helpers;
10+
using Microsoft.CodeAnalysis;
911
using Microsoft.CodeAnalysis.Diagnostics;
1012
using StyleCop.Analyzers.DocumentationRules;
1113
using TestHelper;
@@ -214,6 +216,122 @@ public event System.Action EventName2 {{ add {{ }} remove {{ }} }}
214216
await this.VerifyCSharpDiagnosticAsync(string.Format(testCode, type), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
215217
}
216218

219+
/// <summary>
220+
/// Verifies that a class that includes the inheritdoc will not produce diagnostics.
221+
/// </summary>
222+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
223+
[Fact]
224+
public async Task TestCorrectClassInheritDocAsync()
225+
{
226+
var testCode = @"
227+
/// <summary>Base class</summary>
228+
public class BaseClass { }
229+
230+
/// <include file='ClassInheritDoc.xml' path='/TestClass/*'/>
231+
public class TestClass : BaseClass
232+
{
233+
}
234+
";
235+
236+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
237+
}
238+
239+
/// <summary>
240+
/// Verifies that a class that includes an invalid inheritdoc will produce the expected diagnostics.
241+
/// </summary>
242+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
243+
[Fact]
244+
public async Task TestIncorrectClassInheritDocAsync()
245+
{
246+
var testCode = @"
247+
/// <include file='ClassInheritDoc.xml' path='/TestClass/*'/>
248+
public class TestClass
249+
{
250+
}
251+
";
252+
253+
var expected = this.CSharpDiagnostic().WithLocation(2, 5);
254+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
255+
}
256+
257+
/// <summary>
258+
/// Verifies that a method that includes the inheritdoc will not produce diagnostics.
259+
/// </summary>
260+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
261+
[Fact]
262+
public async Task TestCorrectMethodInheritDocAsync()
263+
{
264+
var testCode = @"
265+
/// <summary>Base class</summary>
266+
public interface ITest
267+
{
268+
/// <summary>My test method,</summary>
269+
void TestMethod();
270+
}
271+
272+
/// <summary>Test class</summary>
273+
public class TestClass : ITest
274+
{
275+
/// <include file='MethodInheritDoc.xml' path='/TestClass/TestMethod/*'/>
276+
public void TestMethod()
277+
{
278+
}
279+
}
280+
";
281+
282+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
283+
}
284+
285+
/// <summary>
286+
/// Verifies that a method that includes an invalid inheritdoc will produce the expected diagnostics.
287+
/// </summary>
288+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
289+
[Fact]
290+
public async Task TestIncorrectMethodInheritDocAsync()
291+
{
292+
var testCode = @"
293+
/// <summary>Base class</summary>
294+
public interface ITest
295+
{
296+
}
297+
298+
/// <summary>Test class</summary>
299+
public class TestClass : ITest
300+
{
301+
/// <include file='MethodInheritDoc.xml' path='/TestClass/TestMethod/*'/>
302+
public void TestMethod() { }
303+
}
304+
";
305+
306+
var expected = this.CSharpDiagnostic().WithLocation(10, 7);
307+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
308+
}
309+
310+
protected override Project ApplyCompilationOptions(Project project)
311+
{
312+
var resolver = new TestXmlReferenceResolver();
313+
314+
string contentClassInheritDoc = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
315+
<TestClass>
316+
<inheritdoc/>
317+
</TestClass>
318+
";
319+
resolver.XmlReferences.Add("ClassInheritDoc.xml", contentClassInheritDoc);
320+
321+
string contentMethodInheritDoc = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
322+
<TestClass>
323+
<TestMethod>
324+
<inheritdoc/>
325+
</TestMethod>
326+
</TestClass>
327+
";
328+
resolver.XmlReferences.Add("MethodInheritDoc.xml", contentMethodInheritDoc);
329+
330+
project = base.ApplyCompilationOptions(project);
331+
project = project.WithCompilationOptions(project.CompilationOptions.WithXmlReferenceResolver(resolver));
332+
return project;
333+
}
334+
217335
/// <inheritdoc/>
218336
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
219337
{

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1648InheritDocMustBeUsedWithInheritingClass.cs

Lines changed: 92 additions & 14 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;
@@ -75,19 +77,55 @@ private static void HandleBaseTypeLikeDeclaration(SyntaxNodeAnalysisContext cont
7577
}
7678

7779
DocumentationCommentTriviaSyntax documentation = context.Node.GetDocumentationCommentTriviaSyntax();
78-
79-
XmlNodeSyntax inheritDocElement = documentation?.Content.GetFirstXmlElement(XmlCommentHelper.InheritdocXmlTag);
80-
if (inheritDocElement == null)
80+
if (documentation == null)
8181
{
8282
return;
8383
}
8484

85-
if (HasXmlCrefAttribute(inheritDocElement))
85+
Location location;
86+
87+
var includeElement = documentation.Content.GetFirstXmlElement(XmlCommentHelper.IncludeXmlTag) as XmlEmptyElementSyntax;
88+
if (includeElement != null)
8689
{
87-
return;
90+
var declaration = context.SemanticModel.GetDeclaredSymbol(baseType, context.CancellationToken);
91+
if (declaration == null)
92+
{
93+
return;
94+
}
95+
96+
var rawDocumentation = declaration.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: context.CancellationToken);
97+
var completeDocumentation = XElement.Parse(rawDocumentation, LoadOptions.None);
98+
99+
var inheritDocElement = completeDocumentation.Nodes().OfType<XElement>().FirstOrDefault(element => element.Name == XmlCommentHelper.InheritdocXmlTag);
100+
if (inheritDocElement == null)
101+
{
102+
return;
103+
}
104+
105+
if (HasXmlCrefAttribute(inheritDocElement))
106+
{
107+
return;
108+
}
109+
110+
location = includeElement.GetLocation();
88111
}
112+
else
113+
{
114+
XmlNodeSyntax inheritDocElement = documentation.Content.GetFirstXmlElement(XmlCommentHelper.InheritdocXmlTag);
115+
if (inheritDocElement == null)
116+
{
117+
return;
118+
}
119+
120+
if (HasXmlCrefAttribute(inheritDocElement))
121+
{
122+
return;
123+
}
89124

90-
context.ReportDiagnostic(Diagnostic.Create(Descriptor, inheritDocElement.GetLocation()));
125+
location = inheritDocElement.GetLocation();
126+
}
127+
128+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, location));
91129
}
92130

93131
private static void HandleMemberDeclaration(SyntaxNodeAnalysisContext context)
@@ -102,17 +140,12 @@ private static void HandleMemberDeclaration(SyntaxNodeAnalysisContext context)
102140
}
103141

104142
DocumentationCommentTriviaSyntax documentation = memberSyntax.GetDocumentationCommentTriviaSyntax();
105-
106-
XmlNodeSyntax inheritDocElement = documentation?.Content.GetFirstXmlElement(XmlCommentHelper.InheritdocXmlTag);
107-
if (inheritDocElement == null)
143+
if (documentation == null)
108144
{
109145
return;
110146
}
111147

112-
if (HasXmlCrefAttribute(inheritDocElement))
113-
{
114-
return;
115-
}
148+
Location location;
116149

117150
ISymbol declaredSymbol = context.SemanticModel.GetDeclaredSymbol(memberSyntax, context.CancellationToken);
118151
if (declaredSymbol == null && memberSyntax.IsKind(SyntaxKind.EventFieldDeclaration))
@@ -125,11 +158,51 @@ private static void HandleMemberDeclaration(SyntaxNodeAnalysisContext context)
125158
}
126159
}
127160

161+
var includeElement = documentation.Content.GetFirstXmlElement(XmlCommentHelper.IncludeXmlTag) as XmlEmptyElementSyntax;
162+
if (includeElement != null)
163+
{
164+
if (declaredSymbol == null)
165+
{
166+
return;
167+
}
168+
169+
var rawDocumentation = declaredSymbol.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: context.CancellationToken);
170+
var completeDocumentation = XElement.Parse(rawDocumentation, LoadOptions.None);
171+
172+
var inheritDocElement = completeDocumentation.Nodes().OfType<XElement>().FirstOrDefault(element => element.Name == XmlCommentHelper.InheritdocXmlTag);
173+
if (inheritDocElement == null)
174+
{
175+
return;
176+
}
177+
178+
if (HasXmlCrefAttribute(inheritDocElement))
179+
{
180+
return;
181+
}
182+
183+
location = includeElement.GetLocation();
184+
}
185+
else
186+
{
187+
XmlNodeSyntax inheritDocElement = documentation.Content.GetFirstXmlElement(XmlCommentHelper.InheritdocXmlTag);
188+
if (inheritDocElement == null)
189+
{
190+
return;
191+
}
192+
193+
if (HasXmlCrefAttribute(inheritDocElement))
194+
{
195+
return;
196+
}
197+
198+
location = inheritDocElement.GetLocation();
199+
}
200+
128201
// If we don't have a declared symbol we have some kind of field declaration. A field can not override or
129202
// implement anything so we want to report a diagnostic.
130203
if (declaredSymbol == null || !NamedTypeHelpers.IsImplementingAnInterfaceMember(declaredSymbol))
131204
{
132-
context.ReportDiagnostic(Diagnostic.Create(Descriptor, inheritDocElement.GetLocation()));
205+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, location));
133206
}
134207
}
135208

@@ -149,5 +222,10 @@ private static bool HasXmlCrefAttribute(XmlNodeSyntax inheritDocElement)
149222

150223
return false;
151224
}
225+
226+
private static bool HasXmlCrefAttribute(XElement inheritDocElement)
227+
{
228+
return inheritDocElement.Attribute(XmlCommentHelper.CrefArgumentName) != null;
229+
}
152230
}
153231
}

0 commit comments

Comments
 (0)