Skip to content

Commit 72111ab

Browse files
committed
Updated SA1651
1 parent 190f3ff commit 72111ab

3 files changed

Lines changed: 122 additions & 8 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/SA1651CodeFixProvider.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
4444
{
4545
foreach (var diagnostic in context.Diagnostics)
4646
{
47+
if (diagnostic.Properties.ContainsKey(SA1651DoNotUsePlaceholderElements.NoCodeFixKey))
48+
{
49+
// skip diagnostics that should not offer a code fix.
50+
continue;
51+
}
52+
4753
var documentRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
4854
SyntaxNode syntax = documentRoot.FindNode(diagnostic.Location.SourceSpan, findInsideTrivia: true, getInnermostNodeForTie: true);
4955
if (syntax == null)

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1651UnitTests.cs

Lines changed: 66 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.CodeFixes;
1012
using Microsoft.CodeAnalysis.Diagnostics;
1113
using StyleCop.Analyzers.DocumentationRules;
@@ -229,6 +231,70 @@ public class ClassName
229231
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
230232
}
231233

234+
/// <summary>
235+
/// Verifies that included documentation without place holders will not produce diagnostics.
236+
/// </summary>
237+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
238+
[Fact]
239+
public async Task TestNoPlaceHolderInIncludedDocumentationAsync()
240+
{
241+
var testCode = @"
242+
/// <include file='SummaryWithoutPlaceHolder.xml' path='/TestClass/*'/>
243+
public class TestClass
244+
{
245+
}
246+
";
247+
248+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
249+
}
250+
251+
/// <summary>
252+
/// Verifies that included documentation with place holders will not produce the expected diagnostics.
253+
/// </summary>
254+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
255+
[Fact]
256+
public async Task TestPlaceHolderInIncludedDocumentationAsync()
257+
{
258+
var testCode = @"
259+
/// <include file='SummaryWithPlaceHolder.xml' path='/TestClass/*'/>
260+
public class TestClass
261+
{
262+
}
263+
";
264+
265+
var expected = this.CSharpDiagnostic().WithLocation(2, 5);
266+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
267+
var offeredFixes = await this.GetOfferedCSharpFixesAsync(testCode).ConfigureAwait(false);
268+
Assert.Empty(offeredFixes);
269+
}
270+
271+
protected override Project ApplyCompilationOptions(Project project)
272+
{
273+
var resolver = new TestXmlReferenceResolver();
274+
275+
string contentSummaryWithoutPlaceHolder = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
276+
<TestClass>
277+
<summary>
278+
This is a test class.
279+
</summary>
280+
</TestClass>
281+
";
282+
resolver.XmlReferences.Add("SummaryWithoutPlaceHolder.xml", contentSummaryWithoutPlaceHolder);
283+
284+
string contentSummaryWithPlaceHolder = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
285+
<TestClass>
286+
<summary>
287+
<placeholder>This is a test class.</placeholder>
288+
</summary>
289+
</TestClass>
290+
";
291+
resolver.XmlReferences.Add("SummaryWithPlaceHolder.xml", contentSummaryWithPlaceHolder);
292+
293+
project = base.ApplyCompilationOptions(project);
294+
project = project.WithCompilationOptions(project.CompilationOptions.WithXmlReferenceResolver(resolver));
295+
return project;
296+
}
297+
232298
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
233299
{
234300
yield return new SA1651DoNotUsePlaceholderElements();

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1651DoNotUsePlaceholderElements.cs

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ namespace StyleCop.Analyzers.DocumentationRules
55
{
66
using System;
77
using System.Collections.Immutable;
8+
using System.Linq;
9+
using System.Xml.Linq;
10+
using Helpers;
811
using Microsoft.CodeAnalysis;
912
using Microsoft.CodeAnalysis.CSharp;
1013
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -37,6 +40,12 @@ internal class SA1651DoNotUsePlaceholderElements : DiagnosticAnalyzer
3740
/// analyzer.
3841
/// </summary>
3942
public const string DiagnosticId = "SA1651";
43+
44+
/// <summary>
45+
/// The key used for signalling that no codefix should be offered.
46+
/// </summary>
47+
internal const string NoCodeFixKey = "NoCodeFix";
48+
4049
private const string Title = "Do not use placeholder elements";
4150
private const string MessageFormat = "Do not use placeholder elements";
4251
private const string Description = "The element documentation contains a <placeholder> element.";
@@ -48,6 +57,8 @@ internal class SA1651DoNotUsePlaceholderElements : DiagnosticAnalyzer
4857
private static readonly Action<SyntaxNodeAnalysisContext> XmlElementAction = HandleXmlElement;
4958
private static readonly Action<SyntaxNodeAnalysisContext> XmlEmptyElementAction = HandleXmlEmptyElement;
5059

60+
private static readonly ImmutableDictionary<string, string> NoCodeFixProperties = ImmutableDictionary.Create<string, string>().Add(NoCodeFixKey, string.Empty);
61+
5162
/// <inheritdoc/>
5263
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
5364
ImmutableArray.Create(Descriptor);
@@ -65,23 +76,54 @@ public override void Initialize(AnalysisContext context)
6576
private static void HandleXmlElement(SyntaxNodeAnalysisContext context)
6677
{
6778
XmlElementSyntax syntax = (XmlElementSyntax)context.Node;
68-
if (!string.Equals("placeholder", syntax.StartTag?.Name?.ToString(), StringComparison.Ordinal))
79+
CheckTag(context, syntax.StartTag?.Name?.ToString());
80+
}
81+
82+
private static void HandleXmlEmptyElement(SyntaxNodeAnalysisContext context)
83+
{
84+
XmlEmptyElementSyntax syntax = (XmlEmptyElementSyntax)context.Node;
85+
CheckTag(context, syntax.Name?.ToString());
86+
}
87+
88+
private static void CheckTag(SyntaxNodeAnalysisContext context, string tagName)
89+
{
90+
if (string.Equals(XmlCommentHelper.IncludeXmlTag, tagName, StringComparison.Ordinal))
6991
{
70-
return;
92+
if (!IncludedDocumentationContainsPlaceHolderTags(context))
93+
{
94+
return;
95+
}
96+
97+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, context.Node.GetLocation(), NoCodeFixProperties));
7198
}
99+
else
100+
{
101+
if (!string.Equals(XmlCommentHelper.PlaceholderTag, tagName, StringComparison.Ordinal))
102+
{
103+
return;
104+
}
72105

73-
context.ReportDiagnostic(Diagnostic.Create(Descriptor, syntax.GetLocation()));
106+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, context.Node.GetLocation()));
107+
}
74108
}
75109

76-
private static void HandleXmlEmptyElement(SyntaxNodeAnalysisContext context)
110+
private static bool IncludedDocumentationContainsPlaceHolderTags(SyntaxNodeAnalysisContext context)
77111
{
78-
XmlEmptyElementSyntax syntax = (XmlEmptyElementSyntax)context.Node;
79-
if (!string.Equals("placeholder", syntax.Name?.ToString(), StringComparison.Ordinal))
112+
var memberDeclaration = context.Node.FirstAncestorOrSelf<MemberDeclarationSyntax>();
113+
if (memberDeclaration == null)
114+
{
115+
return false;
116+
}
117+
118+
var declaration = context.SemanticModel.GetDeclaredSymbol(memberDeclaration, context.CancellationToken);
119+
if (declaration == null)
80120
{
81-
return;
121+
return false;
82122
}
83123

84-
context.ReportDiagnostic(Diagnostic.Create(Descriptor, syntax.GetLocation()));
124+
var rawDocumentation = declaration.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: context.CancellationToken);
125+
var completeDocumentation = XElement.Parse(rawDocumentation, LoadOptions.None);
126+
return completeDocumentation.DescendantNodesAndSelf().OfType<XElement>().Any(element => element.Name == XmlCommentHelper.PlaceholderTag);
85127
}
86128
}
87129
}

0 commit comments

Comments
 (0)