From 4444f14f0b574d4deda7921e3ab6892c823474c7 Mon Sep 17 00:00:00 2001 From: Daniel Palme Date: Sun, 18 Aug 2024 18:59:56 +0200 Subject: [PATCH] #685 Extended "raw mode" for dotCover format (settings:rawMode=true) to disable that coverage data of nested or compiler generated classes is included in the parent class. --- src/Readme.txt | 5 + .../Parser/CoverageReportParser.cs | 5 +- .../Parser/DotCoverParser.cs | 97 +++++++++++-------- 3 files changed, 68 insertions(+), 39 deletions(-) diff --git a/src/Readme.txt b/src/Readme.txt index 4c322849..7d59f206 100644 --- a/src/Readme.txt +++ b/src/Readme.txt @@ -67,6 +67,11 @@ For further details take a look at LICENSE.txt. CHANGELOG +5.3.9.0 + + * New: #685 Extended "raw mode" for dotCover format (settings:rawMode=true) to disable that coverage data of nested or compiler generated + classes is included in the parent class. + 5.3.8.0 * Fix: #681 Updated System.Text.Json to address CVE-2024-30105 (contributed by @304NotModified) diff --git a/src/ReportGenerator.Core/Parser/CoverageReportParser.cs b/src/ReportGenerator.Core/Parser/CoverageReportParser.cs index 53f42731..4f848055 100644 --- a/src/ReportGenerator.Core/Parser/CoverageReportParser.cs +++ b/src/ReportGenerator.Core/Parser/CoverageReportParser.cs @@ -408,7 +408,10 @@ private IEnumerable ParseXmlFile(string filePath) new DotCoverReportPreprocessor().Execute(item); Logger.DebugFormat(Resources.InitiatingParser, "dotCover"); - yield return new DotCoverParser(this.assemblyFilter, this.classFilter, this.fileFilter).Parse(item); + yield return new DotCoverParser(this.assemblyFilter, this.classFilter, this.fileFilter) + { + RawMode = !this.rawModeProhibited && this.reportContext?.Settings.RawMode == true + }.Parse(item); } yield break; diff --git a/src/ReportGenerator.Core/Parser/DotCoverParser.cs b/src/ReportGenerator.Core/Parser/DotCoverParser.cs index 75b2c691..71bbbe9c 100644 --- a/src/ReportGenerator.Core/Parser/DotCoverParser.cs +++ b/src/ReportGenerator.Core/Parser/DotCoverParser.cs @@ -96,20 +96,15 @@ 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")) + var classes = assemblyElement + .Descendants("Type") + .Where(c => this.RawMode || c.Parent.Name != "Type") .Where(c => !Regex.IsMatch(c.Attribute("Name").Value, "<.*>.+__", RegexOptions.Compiled)) - .Select(c => c.Parent.Attribute("Name").Value + "." + c.Attribute("Name").Value) - .Distinct() - .Where(c => this.ClassFilter.IsElementIncludedInReport(c)) - .OrderBy(name => name) .ToArray(); var assembly = new Assembly(assemblyName); - Parallel.ForEach(classNames, className => this.ProcessClass(modules, files, assembly, className)); + Parallel.ForEach(classes, clazz => this.ProcessClass(files, assembly, clazz)); return assembly; } @@ -117,24 +112,47 @@ private Assembly ProcessAssembly(XElement[] modules, XElement[] files, string as /// /// Processes the given class. /// - /// The modules. /// The files. /// The assembly. - /// Name of the class. - private void ProcessClass(XElement[] modules, XElement[] files, Assembly assembly, string className) + /// The class element. + private void ProcessClass(XElement[] files, Assembly assembly, XElement classElement) { - 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") - .Select(c => c.Attribute("FileIndex").Value) - .Distinct() - .ToArray(); + string className = classElement.Attribute("Name").Value; + + XElement parent = classElement.Parent; + + while (parent.Name == "Type") + { + className = parent.Attribute("Name").Value + "." + className; + parent = parent.Parent; + } + + className = parent.Attribute("Name").Value + "." + className; + + if (!this.ClassFilter.IsElementIncludedInReport(className)) + { + return; + } + + string[] fileIdsOfClass; + + if (this.RawMode) + { + fileIdsOfClass = classElement + .Elements("Method") + .Elements("Statement") + .Select(c => c.Attribute("FileIndex").Value) + .Distinct() + .ToArray(); + } + else + { + fileIdsOfClass = classElement + .Descendants("Statement") + .Select(c => c.Attribute("FileIndex").Value) + .Distinct() + .ToArray(); + } var filteredFilesOfClass = fileIdsOfClass .Select(fileId => @@ -153,7 +171,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(file.FileId, classElement, file.FilePath)); } assembly.AddClass(@class); @@ -163,23 +181,26 @@ private void ProcessClass(XElement[] modules, XElement[] files, Assembly assembl /// /// Processes the file. /// - /// The modules. /// The file id. - /// The class. + /// The class element. /// The file path. /// The . - private static CodeFile ProcessFile(XElement[] modules, string fileId, Class @class, string filePath) + private CodeFile ProcessFile(string fileId, XElement classElement, 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") - .ToArray(); + XElement[] methodsOfFile; + + if (this.RawMode) + { + methodsOfFile = classElement + .Elements("Method") + .ToArray(); + } + else + { + methodsOfFile = classElement + .Descendants("Method") + .ToArray(); + } var statements = methodsOfFile .Elements("Statement")