diff --git a/src/ReportGenerator.Core.Test/Parser/DotCoverParserTest.cs b/src/ReportGenerator.Core.Test/Parser/DotCoverParserTest.cs
index 703aa9da..80ff6e09 100644
--- a/src/ReportGenerator.Core.Test/Parser/DotCoverParserTest.cs
+++ b/src/ReportGenerator.Core.Test/Parser/DotCoverParserTest.cs
@@ -107,7 +107,7 @@ public void FilesOfClassTest()
[Fact]
public void ClassesInAssemblyTest()
{
- Assert.Equal(19, this.parserResult.Assemblies.SelectMany(a => a.Classes).Count());
+ Assert.Equal(24, this.parserResult.Assemblies.SelectMany(a => a.Classes).Count());
}
///
@@ -153,8 +153,11 @@ public void MethodMetricsTest()
public void CodeElementsTest()
{
var codeElements = GetFile(this.parserResult.Assemblies, "Test.TestClass", "C:\\temp\\TestClass.cs").CodeElements;
- Assert.Equal(5, codeElements.Count());
+ Assert.Equal(4, codeElements.Count());
+ codeElements = GetFile(this.parserResult.Assemblies, "Test.TestClass.NestedClass", "C:\\temp\\TestClass.cs").CodeElements;
+ Assert.Equal(1, codeElements.Count());
+
codeElements = GetFile(this.parserResult.Assemblies, "Test.PartialClass", "C:\\temp\\PartialClass.cs").CodeElements;
Assert.Equal(4, codeElements.Count());
diff --git a/src/ReportGenerator.Core/Parser/DotCoverParser.cs b/src/ReportGenerator.Core/Parser/DotCoverParser.cs
index 75b2c691..5db24f2f 100644
--- a/src/ReportGenerator.Core/Parser/DotCoverParser.cs
+++ b/src/ReportGenerator.Core/Parser/DotCoverParser.cs
@@ -37,6 +37,11 @@ internal class DotCoverParser : ParserBase
///
private static readonly Regex LocalFunctionMethodNameRegex = new Regex(@"^.*(?<.+>).*__(?[^\|]+)\|.+\((?.*)\):.+$", RegexOptions.Compiled);
+ ///
+ /// Regex to analyze if a type name is a generated nested type (e.g. an async method).
+ ///
+ private static readonly Regex GeneratedClassNameRegex = new Regex("<.*>.+__", RegexOptions.Compiled);
+
///
/// Initializes a new instance of the class.
///
@@ -96,12 +101,9 @@ private Assembly ProcessAssembly(XElement[] modules, XElement[] files, string as
var assemblyElement = modules
.Where(m => m.Attribute("Name").Value.Equals(assemblyName));
- var classNames = assemblyElement
- .Elements("Namespace")
- .Elements("Type")
- .Concat(assemblyElement.Elements("Type"))
- .Where(c => !Regex.IsMatch(c.Attribute("Name").Value, "<.*>.+__", RegexOptions.Compiled))
- .Select(c => c.Parent.Attribute("Name").Value + "." + c.Attribute("Name").Value)
+ var allTypes = this.GetAllTypes(assemblyElement.ToList());
+ var classNames = allTypes
+ .Select(this.GetFullTypeName)
.Distinct()
.Where(c => this.ClassFilter.IsElementIncludedInReport(c))
.OrderBy(name => name)
@@ -126,12 +128,10 @@ private void ProcessClass(XElement[] modules, XElement[] files, Assembly assembl
var assemblyElement = modules
.Where(m => m.Attribute("Name").Value.Equals(assembly.Name));
- var fileIdsOfClass = assemblyElement
- .Elements("Namespace")
- .Elements("Type")
- .Concat(assemblyElement.Elements("Type"))
- .Where(c => (c.Parent.Attribute("Name").Value + "." + c.Attribute("Name").Value).Equals(className))
- .Descendants("Statement")
+ var allTypes = this.GetAllTypes(assemblyElement.ToList());
+ var fileIdsOfClass = allTypes
+ .Where(c => this.GetFullTypeName(c).Equals(className))
+ .SelectMany(c => this.DescendantsNotInOtherType(c, "Statement"))
.Select(c => c.Attribute("FileIndex").Value)
.Distinct()
.ToArray();
@@ -153,7 +153,7 @@ private void ProcessClass(XElement[] modules, XElement[] files, Assembly assembl
foreach (var file in filteredFilesOfClass)
{
- @class.AddFile(ProcessFile(modules, file.FileId, @class, file.FilePath));
+ @class.AddFile(this.ProcessFile(modules, file.FileId, @class, file.FilePath));
}
assembly.AddClass(@class);
@@ -168,17 +168,16 @@ private void ProcessClass(XElement[] modules, XElement[] files, Assembly assembl
/// The class.
/// The file path.
/// The .
- private static CodeFile ProcessFile(XElement[] modules, string fileId, Class @class, string filePath)
+ private CodeFile ProcessFile(XElement[] modules, string fileId, Class @class, string filePath)
{
var assemblyElement = modules
.Where(m => m.Attribute("Name").Value.Equals(@class.Assembly.Name));
- var methodsOfFile = assemblyElement
- .Elements("Namespace")
- .Elements("Type")
- .Concat(assemblyElement.Elements("Type"))
- .Where(c => (c.Parent.Attribute("Name").Value + "." + c.Attribute("Name").Value).Equals(@class.Name))
- .Descendants("Method")
+ var allTypes = this.GetAllTypes(assemblyElement.ToList());
+ var classType = allTypes
+ .Where(c => this.GetFullTypeName(c).Equals(@class.Name));
+ var methodsOfFile = classType
+ .SelectMany( c => this.DescendantsNotInOtherType(c, "Method"))
.ToArray();
var statements = methodsOfFile
@@ -193,13 +192,14 @@ private static CodeFile ProcessFile(XElement[] modules, string fileId, Class @cl
.OrderBy(seqpnt => seqpnt.LineNumberEnd)
.ToArray();
- int[] coverage = new int[] { };
- LineVisitStatus[] lineVisitStatus = new LineVisitStatus[] { };
+ int[] coverage = { };
+ LineVisitStatus[] lineVisitStatus = { };
if (statements.Length > 0)
{
- coverage = new int[statements[statements.LongLength - 1].LineNumberEnd + 1];
- lineVisitStatus = new LineVisitStatus[statements[statements.LongLength - 1].LineNumberEnd + 1];
+ int lastCoveredLine = statements[statements.LongLength - 1].LineNumberEnd + 1;
+ coverage = new int[lastCoveredLine];
+ lineVisitStatus = new LineVisitStatus[lastCoveredLine];
for (int i = 0; i < coverage.Length; i++)
{
@@ -219,7 +219,7 @@ private static CodeFile ProcessFile(XElement[] modules, string fileId, Class @cl
var codeFile = new CodeFile(filePath, coverage, lineVisitStatus);
- SetCodeElements(codeFile, fileId, methodsOfFile);
+ this.SetCodeElements(codeFile, fileId, methodsOfFile);
return codeFile;
}
@@ -230,11 +230,11 @@ private static CodeFile ProcessFile(XElement[] modules, string fileId, Class @cl
/// The code file.
/// The id of the file.
/// The methods.
- private static void SetCodeElements(CodeFile codeFile, string fileId, IEnumerable methods)
+ private void SetCodeElements(CodeFile codeFile, string fileId, IEnumerable methods)
{
foreach (var method in methods)
{
- string methodName = ExtractMethodName(method.Parent.Attribute("Name").Value, method.Attribute("Name").Value);
+ string methodName = ExtractMethodName(this.GetFullTypeName(method.Parent), method.Attribute("Name").Value);
if (LambdaMethodNameRegex.IsMatch(methodName))
{
@@ -304,5 +304,81 @@ private static string ExtractMethodName(string typeName, string methodName)
return methodName.Substring(0, methodName.LastIndexOf(':'));
}
+
+ ///
+ /// Gets the full type name from the provided XElement.
+ ///
+ /// The XElement representing the type.
+ /// The full type name.
+ private string GetFullTypeName(XElement type)
+ {
+ if (type.Name != "Type")
+ {
+ throw new Exception("Element is not a type");
+ }
+
+ var name = type.Attribute("Name").Value;
+ while (type.Parent != null)
+ {
+ type = type.Parent;
+
+ // do not use assembly name
+ if (type.Name == "Assembly")
+ {
+ break;
+ }
+
+ name = $"{type.Attribute("Name").Value}.{name}";
+ }
+
+ return name;
+ }
+
+ ///
+ /// Retrieves all types from the given list of elements.
+ ///
+ /// The list of elements to retrieve types from.
+ /// An array of XElement representing the retrieved types.
+ private XElement[] GetAllTypes(List elements)
+ {
+ var types = elements.Elements("Namespace")
+ .Elements("Type")
+ .Concat(elements.Elements("Type"))
+ .Where(c => !GeneratedClassNameRegex.IsMatch(c.Attribute("Name").Value))
+ .ToList();
+ if (types.Any())
+ {
+ types.AddRange(this.GetAllTypes(types));
+ }
+
+ return types.ToArray();
+ }
+
+ ///
+ /// Retrieves all descendants of the specified element that have the specified name, excluding those that are within a "Type" element whose "Name" attribute matches the generated class name pattern.
+ ///
+ /// The element to search within.
+ /// The name of the descendants to retrieve.
+ /// An enumerable collection of XElement objects representing the descendants of the specified element that have the specified name.
+ private IEnumerable DescendantsNotInOtherType(XElement element, XName name)
+ {
+ foreach (var child in element.Elements())
+ {
+ if (child.Name == "Type" && !GeneratedClassNameRegex.IsMatch(child.Attribute("Name").Value))
+ {
+ continue;
+ }
+
+ if (child.Name == name)
+ {
+ yield return child;
+ }
+
+ foreach (var descendent in this.DescendantsNotInOtherType(child, name))
+ {
+ yield return descendent;
+ }
+ }
+ }
}
}