Skip to content

Commit 0745617

Browse files
committed
Add inspection xml-doc analyzer for duplicate names
1 parent 3b49f91 commit 0745617

File tree

4 files changed

+209
-6
lines changed

4 files changed

+209
-6
lines changed

RubberduckCodeAnalysis/InspectionXmlDocAnalyzer.cs

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ public class InspectionXmlDocAnalyzer : DiagnosticAnalyzer
8787
new LocalizableResourceString(nameof(Resources.MissingNameAttributeDescription), Resources.ResourceManager, typeof(Resources))
8888
);
8989

90+
public const string DuplicateNameAttribute = "DuplicateNameAttribute";
91+
private static readonly DiagnosticDescriptor DuplicateNameAttributeRule = new DiagnosticDescriptor(
92+
DuplicateNameAttribute,
93+
new LocalizableResourceString(nameof(Resources.DuplicateNameAttribute), Resources.ResourceManager, typeof(Resources)),
94+
new LocalizableResourceString(nameof(Resources.DuplicateNameAttributeMessageFormat), Resources.ResourceManager, typeof(Resources)),
95+
new LocalizableResourceString(nameof(Resources.XmlDocAnalyzerCategory), Resources.ResourceManager, typeof(Resources)).ToString(),
96+
DiagnosticSeverity.Error,
97+
true,
98+
new LocalizableResourceString(nameof(Resources.DuplicateNameAttributeDescription), Resources.ResourceManager, typeof(Resources))
99+
);
100+
90101
public const string MissingTypeAttribute = "MissingTypeAttribute";
91102
private static readonly DiagnosticDescriptor MissingTypeAttributeRule = new DiagnosticDescriptor(
92103
MissingTypeAttribute,
@@ -130,7 +141,8 @@ public class InspectionXmlDocAnalyzer : DiagnosticAnalyzer
130141
MissingModuleElementRule,
131142
MissingExampleElementRule,
132143
MissingTypeAttributeRule,
133-
InvalidTypeAttributeRule
144+
InvalidTypeAttributeRule,
145+
DuplicateNameAttributeRule
134146
);
135147

136148
public override void Initialize(AnalysisContext context)
@@ -182,13 +194,16 @@ private static void CheckWhyElement(SymbolAnalysisContext context, INamedTypeSym
182194
}
183195
}
184196

185-
private static void CheckNameAttribute(SymbolAnalysisContext context, XElement element, Location location)
197+
private static string CheckNameAttributeAndReturnValue(SymbolAnalysisContext context, XElement element, Location location)
186198
{
187-
if (!element.Attributes().Any(a => a.Name.LocalName.Equals("name")))
199+
var nameAttribute = element.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("name"));
200+
if (nameAttribute == null)
188201
{
189202
var diagnostic = Diagnostic.Create(MissingNameAttributeRule, location, element.Name.LocalName);
190203
context.ReportDiagnostic(diagnostic);
191204
}
205+
206+
return nameAttribute?.Value;
192207
}
193208

194209
private static void CheckReferenceElement(SymbolAnalysisContext context, INamedTypeSymbol symbol, XElement xml, ICollection<AttributeData> requiredLibAttributes)
@@ -199,12 +214,25 @@ private static void CheckReferenceElement(SymbolAnalysisContext context, INamedT
199214
context.ReportDiagnostic(diagnostic);
200215
}
201216

217+
var xmlRefLibs = new List<string>();
202218
foreach (var element in xml.Elements("reference"))
203219
{
204-
CheckNameAttribute(context, element, symbol.Locations[0]);
220+
var name = CheckNameAttributeAndReturnValue(context, element, symbol.Locations[0]);
221+
if (name != null)
222+
{
223+
xmlRefLibs.Add(name);
224+
}
225+
}
226+
227+
var duplicateNames = xmlRefLibs
228+
.GroupBy(name => name)
229+
.Where(group => group.Count() > 1);
230+
foreach (var name in duplicateNames)
231+
{
232+
var diagnostic = Diagnostic.Create(DuplicateNameAttributeRule, symbol.Locations[0], name, "reference");
233+
context.ReportDiagnostic(diagnostic);
205234
}
206235

207-
var xmlRefLibs = xml.Elements("reference").Select(e => e.Attribute("name")?.Value).ToList();
208236
foreach (var attribute in requiredLibAttributes)
209237
{
210238
var requiredLib = attribute.ConstructorArguments[0].Value.ToString();
@@ -255,11 +283,26 @@ private static void CheckModuleElements(SymbolAnalysisContext context, INamedTyp
255283
context.ReportDiagnostic(diagnostic);
256284
}
257285

286+
var moduleNames = new List<string>();
258287
foreach (var module in example.Elements("module"))
259288
{
260-
CheckNameAttribute(context, module, symbol.Locations[0]);
289+
var moduleName = CheckNameAttributeAndReturnValue(context, module, symbol.Locations[0]);
290+
if (moduleName != null)
291+
{
292+
moduleNames.Add(moduleName);
293+
}
294+
261295
CheckTypeAttribute(context, module, symbol.Locations[0]);
262296
}
297+
298+
var duplicateNames = moduleNames
299+
.GroupBy(name => name)
300+
.Where(group => group.Count() > 1);
301+
foreach (var name in duplicateNames)
302+
{
303+
var diagnostic = Diagnostic.Create(DuplicateNameAttributeRule, symbol.Locations[0], name, "module");
304+
context.ReportDiagnostic(diagnostic);
305+
}
263306
}
264307

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

RubberduckCodeAnalysis/Resources.Designer.cs

Lines changed: 27 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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,4 +285,13 @@
285285
<data name="InvalidTypeAttributeMessageFormat" xml:space="preserve">
286286
<value>The value '{0}' is not legal for the 'type' attribute.</value>
287287
</data>
288+
<data name="DuplicateNameAttribute" xml:space="preserve">
289+
<value>Duplicate value in 'name' attribute</value>
290+
</data>
291+
<data name="DuplicateNameAttributeDescription" xml:space="preserve">
292+
<value>The 'name' attribute is used to uniquely identify an element. Consequently, it has to be unique.</value>
293+
</data>
294+
<data name="DuplicateNameAttributeMessageFormat" xml:space="preserve">
295+
<value>The value '{0}' is used in the 'name' attribute of multiple '{1}' elements.</value>
296+
</data>
288297
</root>

RubberduckTestsCodeAnalysis/InspectionXmlDocAnalyzerTests.cs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,62 @@ public sealed class SomeInspection : IInspection { }
365365
Assert.IsFalse(diagnostics.Any(d => d.Descriptor.Id == InspectionXmlDocAnalyzer.MissingNameAttribute));
366366
}
367367

368+
[Test]
369+
[Category("InspectionXmlDoc")]
370+
public void DuplicateNameAttribute_ReferenceElement()
371+
{
372+
var test = @"
373+
namespace Rubberduck.CodeAnalysis.Inspections.Concrete
374+
{
375+
/// <summary>
376+
/// blablabla
377+
/// </summary>
378+
/// <reference name=""Excel"" />
379+
/// <reference name=""Excel"" />
380+
/// <example hasresult=""true"">
381+
/// <![CDATA[
382+
/// Public Sub DoSomething()
383+
/// ' ...
384+
/// End Sub
385+
/// ]]>
386+
/// </example>
387+
[RequiredLibrary(""Excel"")]
388+
public sealed class SomeInspection : IInspection { }
389+
}
390+
";
391+
392+
var diagnostics = GetDiagnostics(test);
393+
Assert.IsTrue(diagnostics.Any(d => d.Descriptor.Id == InspectionXmlDocAnalyzer.DuplicateNameAttribute));
394+
}
395+
396+
[Test]
397+
[Category("InspectionXmlDoc")]
398+
public void DuplicateNameAttribute_ReferenceElement_Negative()
399+
{
400+
var test = @"
401+
namespace Rubberduck.CodeAnalysis.Inspections.Concrete
402+
{
403+
/// <summary>
404+
/// blablabla
405+
/// </summary>
406+
/// <reference name=""Excel"" />
407+
/// <reference name=""Word"" />
408+
/// <example hasresult=""true"">
409+
/// <![CDATA[
410+
/// Public Sub DoSomething()
411+
/// ' ...
412+
/// End Sub
413+
/// ]]>
414+
/// </example>
415+
[RequiredLibrary(""Excel"")]
416+
public sealed class SomeInspection : IInspection { }
417+
}
418+
";
419+
420+
var diagnostics = GetDiagnostics(test);
421+
Assert.IsFalse(diagnostics.Any(d => d.Descriptor.Id == InspectionXmlDocAnalyzer.DuplicateNameAttribute));
422+
}
423+
368424
[Test][Category("InspectionXmlDoc")]
369425
public void MissingHasResultAttribute_Misspelled()
370426
{
@@ -689,5 +745,73 @@ public sealed class SomeInspection : IInspection {{ }}
689745
var diagnostics = GetDiagnostics(test);
690746
Assert.IsFalse(diagnostics.Any(d => d.Descriptor.Id == InspectionXmlDocAnalyzer.InvalidTypeAttribute));
691747
}
748+
749+
[Test]
750+
[Category("InspectionXmlDoc")]
751+
public void DuplicateNameAttribute_ModuleElement()
752+
{
753+
var test = @"
754+
namespace Rubberduck.CodeAnalysis.Inspections.Concrete
755+
{
756+
/// <summary>
757+
/// blablabla
758+
/// </summary>
759+
/// <example hasresult=""true"">
760+
/// <module name=""MyModule"" type=""Standard Module"">
761+
/// <![CDATA[
762+
/// Public Sub DoSomething()
763+
/// ' ...
764+
/// End Sub
765+
/// ]]>
766+
/// </module>
767+
/// <module name=""MyModule"" type=""Class Module"">
768+
/// <![CDATA[
769+
/// Public Sub DoSomething()
770+
/// ' ...
771+
/// End Sub
772+
/// ]]>
773+
/// </module>
774+
/// </example>
775+
public sealed class SomeInspection : IInspection { }
776+
}
777+
";
778+
779+
var diagnostics = GetDiagnostics(test);
780+
Assert.IsTrue(diagnostics.Any(d => d.Descriptor.Id == InspectionXmlDocAnalyzer.DuplicateNameAttribute));
781+
}
782+
783+
[Test]
784+
[Category("InspectionXmlDoc")]
785+
public void DuplicateNameAttribute_ModuleElement_Negative()
786+
{
787+
var test = @"
788+
namespace Rubberduck.CodeAnalysis.Inspections.Concrete
789+
{
790+
/// <summary>
791+
/// blablabla
792+
/// </summary>
793+
/// <example hasresult=""true"">
794+
/// <module name=""MyModule"" type=""Standard Module"">
795+
/// <![CDATA[
796+
/// Public Sub DoSomething()
797+
/// ' ...
798+
/// End Sub
799+
/// ]]>
800+
/// </module>
801+
/// <module name=""OtherModule"" type=""Class Module"">
802+
/// <![CDATA[
803+
/// Public Sub DoSomething()
804+
/// ' ...
805+
/// End Sub
806+
/// ]]>
807+
/// </module>
808+
/// </example>
809+
public sealed class SomeInspection : IInspection { }
810+
}
811+
";
812+
813+
var diagnostics = GetDiagnostics(test);
814+
Assert.IsFalse(diagnostics.Any(d => d.Descriptor.Id == InspectionXmlDocAnalyzer.DuplicateNameAttribute));
815+
}
692816
}
693817
}

0 commit comments

Comments
 (0)