Skip to content

Commit 44683a8

Browse files
committed
Refactor element documentation base class for reusability
1 parent ebcb9d1 commit 44683a8

6 files changed

Lines changed: 135 additions & 113 deletions

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/ElementDocumentationParameterBase.cs renamed to StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/ElementDocumentationBase.cs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ namespace StyleCop.Analyzers.DocumentationRules
1616
/// <summary>
1717
/// This is the base class for analyzers which examine the <c>&lt;param&gt;</c> text of a documentation comment on an element declaration.
1818
/// </summary>
19-
internal abstract class ElementDocumentationParameterBase : DiagnosticAnalyzer
19+
internal abstract class ElementDocumentationBase : DiagnosticAnalyzer
2020
{
21+
private readonly string matchElementName;
2122
private readonly bool inheritDocSuppressesWarnings;
2223

2324
private readonly Action<CompilationStartAnalysisContext> compilationStartAction;
@@ -28,8 +29,9 @@ internal abstract class ElementDocumentationParameterBase : DiagnosticAnalyzer
2829
private readonly Action<SyntaxNodeAnalysisContext> operatorDeclarationAction;
2930
private readonly Action<SyntaxNodeAnalysisContext> conversionOperatorDeclarationAction;
3031

31-
protected ElementDocumentationParameterBase(bool inheritDocSuppressesWarnings)
32+
protected ElementDocumentationBase(string matchElementName, bool inheritDocSuppressesWarnings)
3233
{
34+
this.matchElementName = matchElementName;
3335
this.inheritDocSuppressesWarnings = inheritDocSuppressesWarnings;
3436

3537
this.compilationStartAction = this.HandleCompilationStart;
@@ -53,12 +55,19 @@ public override void Initialize(AnalysisContext context)
5355
/// <param name="context">The current analysis context.</param>
5456
/// <param name="syntaxList">The <see cref="XmlElementSyntax"/> or <see cref="XmlEmptyElementSyntax"/> of the node
5557
/// to examine.</param>
58+
/// <param name="diagnosticLocations">The location(s) where diagnostics, if any, should be reported.</param>
59+
protected abstract void HandleXmlElement(SyntaxNodeAnalysisContext context, IEnumerable<XmlNodeSyntax> syntaxList, params Location[] diagnosticLocations);
60+
61+
/// <summary>
62+
/// Analyzes the top-level <c>&lt;param&gt;</c> elements of a documentation comment.
63+
/// </summary>
64+
/// <param name="context">The current analysis context.</param>
5665
/// <param name="completeDocumentation">The complete documentation for the declared symbol, with any
5766
/// <c>&lt;include&gt;</c> elements expanded. If the XML documentation comment included a <c>&lt;param&gt;</c>
5867
/// element, this value will be <see langword="null"/>, even if the XML documentation comment also included an
5968
/// <c>&lt;include&gt;</c> element.</param>
6069
/// <param name="diagnosticLocations">The location(s) where diagnostics, if any, should be reported.</param>
61-
protected abstract void HandleXmlElement(SyntaxNodeAnalysisContext context, IEnumerable<XmlNodeSyntax> syntaxList, XElement completeDocumentation, params Location[] diagnosticLocations);
70+
protected abstract void HandleCompleteDocumentation(SyntaxNodeAnalysisContext context, XElement completeDocumentation, params Location[] diagnosticLocations);
6271

6372
private void HandleCompilationStart(CompilationStartAnalysisContext context)
6473
{
@@ -148,27 +157,29 @@ private void HandleDeclaration(SyntaxNodeAnalysisContext context, SyntaxNode nod
148157
return;
149158
}
150159

151-
XElement completeDocumentation = null;
152-
var paramXmlElements = documentation.Content.GetXmlElements(XmlCommentHelper.ParamXmlTag);
153-
if (!paramXmlElements.Any())
160+
var matchingXmlElements = documentation.Content.GetXmlElements(this.matchElementName);
161+
if (!matchingXmlElements.Any())
154162
{
155163
var includedDocumentation = documentation.Content.GetFirstXmlElement(XmlCommentHelper.IncludeXmlTag);
156164
if (includedDocumentation != null)
157165
{
158166
var declaration = context.SemanticModel.GetDeclaredSymbol(node, context.CancellationToken);
159167
var rawDocumentation = declaration?.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: context.CancellationToken);
160-
completeDocumentation = XElement.Parse(rawDocumentation, LoadOptions.None);
168+
var completeDocumentation = XElement.Parse(rawDocumentation, LoadOptions.None);
161169

162170
if (this.inheritDocSuppressesWarnings &&
163171
completeDocumentation.Nodes().OfType<XElement>().Any(element => element.Name == XmlCommentHelper.InheritdocXmlTag))
164172
{
165173
// Ignore nodes with an <inheritdoc/> tag in the included XML.
166174
return;
167175
}
176+
177+
this.HandleCompleteDocumentation(context, completeDocumentation, locations);
178+
return; // done
168179
}
169180
}
170181

171-
this.HandleXmlElement(context, paramXmlElements, completeDocumentation, locations);
182+
this.HandleXmlElement(context, matchingXmlElements, locations);
172183
}
173184
}
174185
}

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1611ElementParametersMustBeDocumented.cs

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ namespace StyleCop.Analyzers.DocumentationRules
2929
/// more of its parameters.</para>
3030
/// </remarks>
3131
[DiagnosticAnalyzer(LanguageNames.CSharp)]
32-
internal class SA1611ElementParametersMustBeDocumented : ElementDocumentationParameterBase
32+
internal class SA1611ElementParametersMustBeDocumented : ElementDocumentationBase
3333
{
3434
/// <summary>
3535
/// The ID for diagnostics produced by the <see cref="SA1611ElementParametersMustBeDocumented"/> analyzer.
@@ -44,7 +44,7 @@ internal class SA1611ElementParametersMustBeDocumented : ElementDocumentationPar
4444
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.DocumentationRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
4545

4646
public SA1611ElementParametersMustBeDocumented()
47-
: base(inheritDocSuppressesWarnings: true)
47+
: base(matchElementName: XmlCommentHelper.ParamXmlTag, inheritDocSuppressesWarnings: true)
4848
{
4949
}
5050

@@ -53,7 +53,7 @@ public SA1611ElementParametersMustBeDocumented()
5353
ImmutableArray.Create(Descriptor);
5454

5555
/// <inheritdoc/>
56-
protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, IEnumerable<XmlNodeSyntax> syntaxList, XElement completeDocumentation, params Location[] diagnosticLocations)
56+
protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, IEnumerable<XmlNodeSyntax> syntaxList, params Location[] diagnosticLocations)
5757
{
5858
var node = context.Node;
5959
var parameterList = GetParameters(node);
@@ -62,28 +62,34 @@ protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, IEnu
6262
return;
6363
}
6464

65-
if (completeDocumentation != null)
66-
{
67-
// We are working with an <include> element
68-
var paramElements = completeDocumentation.Nodes()
69-
.OfType<XElement>()
70-
.Where(e => e.Name == XmlCommentHelper.ParamXmlTag);
65+
var xmlParameterNames = syntaxList
66+
.Select(XmlCommentHelper.GetFirstAttributeOrDefault<XmlNameAttributeSyntax>)
67+
.Where(x => x != null)
68+
.Select(x => x.Identifier.Identifier.ValueText);
7169

72-
var xmlParameterNames = paramElements
73-
.SelectMany(p => p.Attributes().Where(a => a.Name == "name"))
74-
.Select(a => a.Value);
70+
ReportMissingParameters(context, parameterList, xmlParameterNames);
71+
}
7572

76-
ReportMissingParameters(context, parameterList, xmlParameterNames);
77-
}
78-
else if (syntaxList != null)
73+
/// <inheritdoc/>
74+
protected override void HandleCompleteDocumentation(SyntaxNodeAnalysisContext context, XElement completeDocumentation, params Location[] diagnosticLocations)
75+
{
76+
var node = context.Node;
77+
var parameterList = GetParameters(node);
78+
if (parameterList == null)
7979
{
80-
var xmlParameterNames = syntaxList
81-
.Select(XmlCommentHelper.GetFirstAttributeOrDefault<XmlNameAttributeSyntax>)
82-
.Where(x => x != null)
83-
.Select(x => x.Identifier.Identifier.ValueText);
84-
85-
ReportMissingParameters(context, parameterList, xmlParameterNames);
80+
return;
8681
}
82+
83+
// We are working with an <include> element
84+
var paramElements = completeDocumentation.Nodes()
85+
.OfType<XElement>()
86+
.Where(e => e.Name == XmlCommentHelper.ParamXmlTag);
87+
88+
var xmlParameterNames = paramElements
89+
.SelectMany(p => p.Attributes().Where(a => a.Name == "name"))
90+
.Select(a => a.Value);
91+
92+
ReportMissingParameters(context, parameterList, xmlParameterNames);
8793
}
8894

8995
private static IEnumerable<ParameterSyntax> GetParameters(SyntaxNode node)

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1612ElementParameterDocumentationMustMatchElementParameters.cs

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace StyleCop.Analyzers.DocumentationRules
2828
/// parameters on the element, or if the parameter documentation is not listed in the same order as the element's parameters.</para>
2929
/// </remarks>
3030
[DiagnosticAnalyzer(LanguageNames.CSharp)]
31-
internal class SA1612ElementParameterDocumentationMustMatchElementParameters : ElementDocumentationParameterBase
31+
internal class SA1612ElementParameterDocumentationMustMatchElementParameters : ElementDocumentationBase
3232
{
3333
/// <summary>
3434
/// The ID for diagnostics produced by the
@@ -49,7 +49,7 @@ internal class SA1612ElementParameterDocumentationMustMatchElementParameters : E
4949
new DiagnosticDescriptor(DiagnosticId, Title, ParamWrongOrderMessageFormat, AnalyzerCategory.DocumentationRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
5050

5151
public SA1612ElementParameterDocumentationMustMatchElementParameters()
52-
: base(inheritDocSuppressesWarnings: true)
52+
: base(matchElementName: XmlCommentHelper.ParamXmlTag, inheritDocSuppressesWarnings: true)
5353
{
5454
}
5555

@@ -58,7 +58,7 @@ public SA1612ElementParameterDocumentationMustMatchElementParameters()
5858
ImmutableArray.Create(MissingParameterDescriptor);
5959

6060
/// <inheritdoc/>
61-
protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, IEnumerable<XmlNodeSyntax> syntaxList, XElement completeDocumentation, params Location[] diagnosticLocations)
61+
protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, IEnumerable<XmlNodeSyntax> syntaxList, params Location[] diagnosticLocations)
6262
{
6363
var node = context.Node;
6464
var identifierLocation = GetIdentifier(node).GetLocation();
@@ -70,27 +70,36 @@ protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, IEnu
7070
return;
7171
}
7272

73-
bool includeElementPresent = completeDocumentation != null;
74-
if (includeElementPresent)
73+
var xmlParameterNames = syntaxList
74+
.Select(XmlCommentHelper.GetFirstAttributeOrDefault<XmlNameAttributeSyntax>)
75+
.Select(x => new Tuple<string, Location>(x?.Identifier?.Identifier.ValueText, x?.Identifier.GetLocation()))
76+
.ToImmutableArray();
77+
78+
VerifyParameters(context, parameterList.Value, xmlParameterNames, identifierLocation);
79+
}
80+
81+
/// <inheritdoc/>
82+
protected override void HandleCompleteDocumentation(SyntaxNodeAnalysisContext context, XElement completeDocumentation, params Location[] diagnosticLocations)
83+
{
84+
var node = context.Node;
85+
var identifierLocation = GetIdentifier(node).GetLocation();
86+
var parameterList = GetParameters(node)?.ToImmutableArray();
87+
88+
bool hasNoParameters = !parameterList?.Any() ?? false;
89+
if (hasNoParameters)
7590
{
76-
var xmlParameterNames = completeDocumentation.Nodes()
77-
.OfType<XElement>()
78-
.Where(e => e.Name == XmlCommentHelper.ParamXmlTag)
79-
.SelectMany(p => p.Attributes().Where(a => a.Name == "name"))
80-
.Select(a => new Tuple<string, Location>(a.Value, null))
81-
.ToImmutableArray();
82-
83-
VerifyParameters(context, parameterList.Value, xmlParameterNames, identifierLocation);
91+
return;
8492
}
85-
else if (syntaxList != null)
86-
{
87-
var xmlParameterNames = syntaxList
88-
.Select(XmlCommentHelper.GetFirstAttributeOrDefault<XmlNameAttributeSyntax>)
89-
.Select(x => new Tuple<string, Location>(x?.Identifier?.Identifier.ValueText, x?.Identifier.GetLocation()))
90-
.ToImmutableArray();
9193

92-
VerifyParameters(context, parameterList.Value, xmlParameterNames, identifierLocation);
93-
}
94+
// We are working with an <include> element
95+
var xmlParameterNames = completeDocumentation.Nodes()
96+
.OfType<XElement>()
97+
.Where(e => e.Name == XmlCommentHelper.ParamXmlTag)
98+
.SelectMany(p => p.Attributes().Where(a => a.Name == "name"))
99+
.Select(a => new Tuple<string, Location>(a.Value, null))
100+
.ToImmutableArray();
101+
102+
VerifyParameters(context, parameterList.Value, xmlParameterNames, identifierLocation);
94103
}
95104

96105
private static void VerifyParameters(SyntaxNodeAnalysisContext context, ImmutableArray<ParameterSyntax> parentParameters, ImmutableArray<Tuple<string, Location>> documentationParameters, Location identifierLocation)

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1613ElementParameterDocumentationMustDeclareParameterName.cs

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace StyleCop.Analyzers.DocumentationRules
2828
/// which is missing a <c>name</c> attribute, or which contains an empty <c>name</c> attribute.</para>
2929
/// </remarks>
3030
[DiagnosticAnalyzer(LanguageNames.CSharp)]
31-
internal class SA1613ElementParameterDocumentationMustDeclareParameterName : ElementDocumentationParameterBase
31+
internal class SA1613ElementParameterDocumentationMustDeclareParameterName : ElementDocumentationBase
3232
{
3333
/// <summary>
3434
/// The ID for diagnostics produced by the
@@ -48,7 +48,7 @@ internal class SA1613ElementParameterDocumentationMustDeclareParameterName : Ele
4848
/// </summary>
4949
/// <remarks>The presence of a &lt;inheritdoc/&gt; tag should NOT suppress warnings from this diagnostic. See DotNetAnalyzers/StyleCopAnalyzers#631</remarks>
5050
public SA1613ElementParameterDocumentationMustDeclareParameterName()
51-
: base(inheritDocSuppressesWarnings: false)
51+
: base(matchElementName: XmlCommentHelper.ParamXmlTag, inheritDocSuppressesWarnings: false)
5252
{
5353
}
5454

@@ -57,44 +57,42 @@ public SA1613ElementParameterDocumentationMustDeclareParameterName()
5757
ImmutableArray.Create(Descriptor);
5858

5959
/// <inheritdoc/>
60-
protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, IEnumerable<XmlNodeSyntax> syntaxList, XElement completeDocumentation, params Location[] diagnosticLocations)
60+
protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, IEnumerable<XmlNodeSyntax> syntaxList, params Location[] diagnosticLocations)
6161
{
62-
bool includeElementPresent = completeDocumentation != null;
63-
if (includeElementPresent)
64-
{
65-
var xmlParameterNames = completeDocumentation.Nodes()
66-
.OfType<XElement>()
67-
.Where(e => e.Name == XmlCommentHelper.ParamXmlTag)
68-
.Select(x =>
62+
var xmlParameterNames = syntaxList
63+
.Where(x => string.Equals(GetName(x)?.ToString(), XmlCommentHelper.ParamXmlTag))
64+
.Select(x =>
65+
{
66+
var nameAttribute = XmlCommentHelper.GetFirstAttributeOrDefault<XmlNameAttributeSyntax>(x);
67+
var location = x.GetLocation();
68+
69+
if (nameAttribute != null)
6970
{
70-
var name = x.Attributes().FirstOrDefault(a => a.Name == "name")?.Value;
71+
location = nameAttribute.GetLocation();
72+
}
7173

72-
return new Tuple<string, Location>(name, null);
73-
})
74-
.ToImmutableArray();
74+
return new Tuple<string, Location>(nameAttribute?.Identifier?.Identifier.ValueText, location);
75+
})
76+
.ToImmutableArray();
7577

76-
VerifyParameters(context, xmlParameterNames, diagnosticLocations.First());
77-
}
78-
else if (syntaxList != null)
79-
{
80-
var xmlParameterNames = syntaxList
81-
.Where(x => string.Equals(GetName(x)?.ToString(), XmlCommentHelper.ParamXmlTag))
82-
.Select(x =>
83-
{
84-
var nameAttribute = XmlCommentHelper.GetFirstAttributeOrDefault<XmlNameAttributeSyntax>(x);
85-
var location = x.GetLocation();
78+
VerifyParameters(context, xmlParameterNames, diagnosticLocations.First());
79+
}
8680

87-
if (nameAttribute != null)
88-
{
89-
location = nameAttribute.GetLocation();
90-
}
81+
/// <inheritdoc/>
82+
protected override void HandleCompleteDocumentation(SyntaxNodeAnalysisContext context, XElement completeDocumentation, params Location[] diagnosticLocations)
83+
{
84+
var xmlParameterNames = completeDocumentation.Nodes()
85+
.OfType<XElement>()
86+
.Where(e => e.Name == XmlCommentHelper.ParamXmlTag)
87+
.Select(x =>
88+
{
89+
var name = x.Attributes().FirstOrDefault(a => a.Name == "name")?.Value;
9190

92-
return new Tuple<string, Location>(nameAttribute?.Identifier?.Identifier.ValueText, location);
93-
})
94-
.ToImmutableArray();
91+
return new Tuple<string, Location>(name, null);
92+
})
93+
.ToImmutableArray();
9594

96-
VerifyParameters(context, xmlParameterNames, diagnosticLocations.First());
97-
}
95+
VerifyParameters(context, xmlParameterNames, diagnosticLocations.First());
9896
}
9997

10098
private static void VerifyParameters(SyntaxNodeAnalysisContext context, ImmutableArray<Tuple<string, Location>> documentationParameters, Location identifierLocation)

0 commit comments

Comments
 (0)