Skip to content

Commit 98ea837

Browse files
committed
Add analyzer for hostApp in inspections xml-doc
1 parent 5d3b03b commit 98ea837

File tree

5 files changed

+405
-42
lines changed

5 files changed

+405
-42
lines changed

Rubberduck.CodeAnalysis/Inspections/Concrete/Excel/SheetAccessedUsingStringInspection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ namespace Rubberduck.CodeAnalysis.Inspections.Concrete.Excel
2323
/// which cannot be altered by the user without accessing the VBE and altering the VBA project.
2424
/// </why>
2525
/// <reference name="Excel" />
26-
/// <hostapp name="EXCEL.EXE" />
26+
/// <hostApp name="EXCEL.EXE" />
2727
/// <remarks>
2828
/// For performance reasons, the inspection only evaluates hard-coded string literals; string-valued expressions evaluating into a sheet name are ignored.
2929
/// </remarks>

RubberduckCodeAnalysis/InspectionXmlDocAnalyzer.cs

Lines changed: 77 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,28 @@ public class InspectionXmlDocAnalyzer : DiagnosticAnalyzer
5454
new LocalizableResourceString(nameof(Resources.MissingRequiredLibAttributeDescription), Resources.ResourceManager, typeof(Resources))
5555
);
5656

57+
public const string MissingHostAppElement = "MissingHostAppElement";
58+
private static readonly DiagnosticDescriptor MissingHostAppElementRule = new DiagnosticDescriptor(
59+
MissingHostAppElement,
60+
new LocalizableResourceString(nameof(Resources.MissingInspectionHostAppElement), Resources.ResourceManager, typeof(Resources)),
61+
new LocalizableResourceString(nameof(Resources.MissingInspectionHostAppElementMessageFormat), Resources.ResourceManager, typeof(Resources)),
62+
new LocalizableResourceString(nameof(Resources.XmlDocAnalyzerCategory), Resources.ResourceManager, typeof(Resources)).ToString(),
63+
DiagnosticSeverity.Error,
64+
true,
65+
new LocalizableResourceString(nameof(Resources.MissingInspectionHostAppElementDescription), Resources.ResourceManager, typeof(Resources))
66+
);
67+
68+
public const string MissingRequiredHostAttribute = "MissingRequiredHostAttribute";
69+
private static readonly DiagnosticDescriptor MissingRequiredHostAttributeRule = new DiagnosticDescriptor(
70+
MissingRequiredHostAttribute,
71+
new LocalizableResourceString(nameof(Resources.MissingRequiredHostAttribute), Resources.ResourceManager, typeof(Resources)),
72+
new LocalizableResourceString(nameof(Resources.MissingRequiredHostAttributeMessageFormat), Resources.ResourceManager, typeof(Resources)),
73+
new LocalizableResourceString(nameof(Resources.XmlDocAnalyzerCategory), Resources.ResourceManager, typeof(Resources)).ToString(),
74+
DiagnosticSeverity.Error,
75+
true,
76+
new LocalizableResourceString(nameof(Resources.MissingRequiredHostAttributeDescription), Resources.ResourceManager, typeof(Resources))
77+
);
78+
5779
public const string MissingExampleElement = "MissingExampleElement";
5880
private static readonly DiagnosticDescriptor MissingExampleElementRule = new DiagnosticDescriptor(
5981
MissingExampleElement,
@@ -142,7 +164,9 @@ public class InspectionXmlDocAnalyzer : DiagnosticAnalyzer
142164
MissingExampleElementRule,
143165
MissingTypeAttributeRule,
144166
InvalidTypeAttributeRule,
145-
DuplicateNameAttributeRule
167+
DuplicateNameAttributeRule,
168+
MissingHostAppElementRule,
169+
MissingRequiredHostAttributeRule
146170
);
147171

148172
public override void Initialize(AnalysisContext context)
@@ -164,9 +188,19 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context)
164188
CheckWhyElement(context, namedTypeSymbol, xml);
165189
CheckExampleElement(context, namedTypeSymbol, xml);
166190

167-
var requiredLibraryAttributes = namedTypeSymbol.GetAttributes().Where(a => a.AttributeClass.Name == "RequiredLibraryAttribute").ToList();
168-
CheckReferenceElement(context, namedTypeSymbol, xml, requiredLibraryAttributes);
169-
CheckRequiredLibAttribute(context, namedTypeSymbol, xml, requiredLibraryAttributes);
191+
var attributes = namedTypeSymbol.GetAttributes();
192+
var requiredLibraryAttributes = attributes
193+
.Where(a => a.AttributeClass.Name == "RequiredLibraryAttribute")
194+
.ToList();
195+
var requiredHostAttributes = attributes
196+
.Where(a => a.AttributeClass.Name == "RequiredHostAttribute")
197+
.ToList();
198+
199+
CheckAttributeRelatedElementElements(context, namedTypeSymbol, xml, requiredLibraryAttributes, "reference", MissingReferenceElementRule);
200+
CheckAttributeRelatedElementElements(context, namedTypeSymbol, xml, requiredHostAttributes, "hostApp", MissingHostAppElementRule);
201+
202+
CheckXmlRelatedAttribute(context, namedTypeSymbol, xml, requiredLibraryAttributes, "reference", MissingRequiredLibAttributeRule);
203+
CheckXmlRelatedAttribute(context, namedTypeSymbol, xml, requiredHostAttributes, "hostApp", MissingRequiredHostAttributeRule);
170204
}
171205

172206
private static bool IsInspectionClass(INamedTypeSymbol namedTypeSymbol)
@@ -206,54 +240,66 @@ private static string CheckNameAttributeAndReturnValue(SymbolAnalysisContext con
206240
return nameAttribute?.Value;
207241
}
208242

209-
private static void CheckReferenceElement(SymbolAnalysisContext context, INamedTypeSymbol symbol, XElement xml, ICollection<AttributeData> requiredLibAttributes)
243+
private static void CheckAttributeRelatedElementElements(SymbolAnalysisContext context, INamedTypeSymbol symbol, XElement xml, ICollection<AttributeData> requiredAttributes, string xmlElementName, DiagnosticDescriptor requiredElementDescriptor)
210244
{
211-
if (requiredLibAttributes.Any() && !xml.Elements("reference").Any())
245+
if (requiredAttributes.Any() && !xml.Elements(xmlElementName).Any())
212246
{
213-
var diagnostic = Diagnostic.Create(MissingReferenceElementRule, symbol.Locations[0], symbol.Name);
247+
var diagnostic = Diagnostic.Create(requiredElementDescriptor, symbol.Locations[0], symbol.Name);
214248
context.ReportDiagnostic(diagnostic);
215249
}
216250

217-
var xmlRefLibs = new List<string>();
218-
foreach (var element in xml.Elements("reference"))
251+
var xmlElementNames = new List<string>();
252+
foreach (var element in xml.Elements(xmlElementName))
219253
{
220254
var name = CheckNameAttributeAndReturnValue(context, element, symbol.Locations[0]);
221255
if (name != null)
222256
{
223-
xmlRefLibs.Add(name);
257+
xmlElementNames.Add(name);
224258
}
225259
}
226260

227-
var duplicateNames = xmlRefLibs
228-
.GroupBy(name => name)
229-
.Where(group => group.Count() > 1)
230-
.Select(group => group.Key);
231-
foreach (var name in duplicateNames)
232-
{
233-
var diagnostic = Diagnostic.Create(DuplicateNameAttributeRule, symbol.Locations[0], name, "reference");
234-
context.ReportDiagnostic(diagnostic);
235-
}
236-
237-
foreach (var attribute in requiredLibAttributes)
261+
CheckForDuplicateNames(context, symbol, xmlElementName, xmlElementNames);
262+
263+
var requiredNames = requiredAttributes
264+
.Where(a => a.ConstructorArguments.Length > 0)
265+
.Select(a => a.ConstructorArguments[0].Value.ToString())
266+
.ToList();
267+
foreach (var requiredName in requiredNames)
238268
{
239-
var requiredLib = attribute.ConstructorArguments[0].Value.ToString();
240-
if (xmlRefLibs.All(lib => lib != requiredLib))
269+
if (requiredNames.All(lib => lib != requiredName))
241270
{
242-
var diagnostic = Diagnostic.Create(MissingReferenceElementRule, symbol.Locations[0], symbol.Name);
271+
var diagnostic = Diagnostic.Create(requiredElementDescriptor, symbol.Locations[0], symbol.Name);
243272
context.ReportDiagnostic(diagnostic);
244273
}
245274
}
246275
}
247276

248-
private static void CheckRequiredLibAttribute(SymbolAnalysisContext context, INamedTypeSymbol symbol, XElement xml, IEnumerable<AttributeData> requiredLibAttributes)
277+
private static void CheckForDuplicateNames(SymbolAnalysisContext context, INamedTypeSymbol symbol, string xmlElementName, List<string> names)
278+
{
279+
var duplicateNames = names
280+
.GroupBy(name => name)
281+
.Where(group => @group.Count() > 1)
282+
.Select(group => @group.Key);
283+
foreach (var name in duplicateNames)
284+
{
285+
var diagnostic = Diagnostic.Create(DuplicateNameAttributeRule, symbol.Locations[0], name, xmlElementName);
286+
context.ReportDiagnostic(diagnostic);
287+
}
288+
}
289+
290+
private static void CheckXmlRelatedAttribute(SymbolAnalysisContext context, INamedTypeSymbol symbol, XElement xml, IEnumerable<AttributeData> requiredAttributes, string xmlElementName, DiagnosticDescriptor requiredAttributeDescriptor)
249291
{
250-
var requiredLibs = requiredLibAttributes.Select(a => a.ConstructorArguments[0].Value.ToString()).ToList();
251-
foreach (var element in xml.Elements("reference"))
292+
var requiredNames = requiredAttributes
293+
.Where(a => a.ConstructorArguments.Length > 0)
294+
.Select(a => a.ConstructorArguments[0].Value.ToString())
295+
.ToList();
296+
297+
foreach (var element in xml.Elements(xmlElementName))
252298
{
253-
var xmlRefLib = element.Attribute("name")?.Value;
254-
if (xmlRefLib == null || requiredLibs.All(lib => lib != xmlRefLib))
299+
var name = element.Attribute("name")?.Value;
300+
if (name == null || requiredNames.All(lib => lib != name))
255301
{
256-
var diagnostic = Diagnostic.Create(MissingRequiredLibAttributeRule, symbol.Locations[0], symbol.Name, xmlRefLib);
302+
var diagnostic = Diagnostic.Create(requiredAttributeDescriptor, symbol.Locations[0], symbol.Name, name);
257303
context.ReportDiagnostic(diagnostic);
258304
}
259305
}
@@ -296,15 +342,7 @@ private static void CheckModuleElements(SymbolAnalysisContext context, INamedTyp
296342
CheckTypeAttribute(context, module, symbol.Locations[0]);
297343
}
298344

299-
var duplicateNames = moduleNames
300-
.GroupBy(name => name)
301-
.Where(group => group.Count() > 1)
302-
.Select(group => group.Key);
303-
foreach (var name in duplicateNames)
304-
{
305-
var diagnostic = Diagnostic.Create(DuplicateNameAttributeRule, symbol.Locations[0], name, "module");
306-
context.ReportDiagnostic(diagnostic);
307-
}
345+
CheckForDuplicateNames(context, symbol, "module", moduleNames);
308346
}
309347

310348
private static void CheckHasResultAttribute(SymbolAnalysisContext context, XElement element, Location location)

RubberduckCodeAnalysis/Resources.Designer.cs

Lines changed: 54 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

RubberduckCodeAnalysis/Resources.resx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,4 +294,22 @@
294294
<data name="DuplicateNameAttributeMessageFormat" xml:space="preserve">
295295
<value>The value '{0}' is used in the 'name' attribute of multiple '{1}' elements.</value>
296296
</data>
297+
<data name="MissingRequiredHostAttribute" xml:space="preserve">
298+
<value>Missing 'RequiredHost' attribute</value>
299+
</data>
300+
<data name="MissingRequiredHostAttributeDescription" xml:space="preserve">
301+
<value>The &lt;hostApp name="RequiredHost" /&gt; element means to document the presence of a [RequiredHostAttribute]. If the attribute is correctly missing, the xml-doc element should be removed.</value>
302+
</data>
303+
<data name="MissingRequiredHostAttributeMessageFormat" xml:space="preserve">
304+
<value>XML documentation of type '{0}' includes a &lt;hostApp&gt; element, but no corresponding [RequiredHostAttribute] is decorating the inspection type. Expected: [RequiredHost("{1}")].</value>
305+
</data>
306+
<data name="MissingInspectionHostAppElement" xml:space="preserve">
307+
<value>Missing xml-doc 'hostApp' element</value>
308+
</data>
309+
<data name="MissingInspectionHostAppElementDescription" xml:space="preserve">
310+
<value>XML documentation for inspections with a [RequiredHostAttribute] must include a &lt;hostApp&gt; element with a 'name' attribute with the same value as the [RequiredHostAttribute]. For example [RequiredHost("Excel")] mandates &lt;hostApp name="Excel" /&gt;.</value>
311+
</data>
312+
<data name="MissingInspectionHostAppElementMessageFormat" xml:space="preserve">
313+
<value>XML documentation for type '{0}' is missing a 'hostApp' element.</value>
314+
</data>
297315
</root>

0 commit comments

Comments
 (0)