< Summary

Information
Class: DirectSight.Parser.OpenCoverParser
Assembly DirectSight
File(s): /home/runner/work/DirectSight/DirectSight/DirectSight/Parser/OpenCoverParser.cs
Line coverage
94%
Covered lines: 390
Uncovered lines: 24
Coverable lines: 414
Total lines: 635
Line coverage: 94.2%
Branch coverage
85%
Covered branches: 115
Total branches: 134
Branch coverage: 85.8%
Method coverage

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%11100%
.ctor()100%11100%
Parse(...)87.5%8885.36%
ProcessAssembly(...)50%8882.6%
ProcessClass(...)80%1010100%
ProcessFile(...)89.13%4646100%
SetMethodMetrics(...)95%2020100%
GetBranches(...)93.75%161693.02%
SetCodeElements(...)85.71%1414100%
ExtractMethodName(...)70%101070.83%
.ctor(...)100%22100%

File(s)

/home/runner/work/DirectSight/DirectSight/DirectSight/Parser/OpenCoverParser.cs

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Collections.Generic;
 4using System.Linq;
 5using System.Text.RegularExpressions;
 6using System.Threading.Tasks;
 7using System.Xml.Linq;
 8using DirectSight.Common;
 9using DirectSight.Logging;
 10using DirectSight.Parser.Analysis;
 11using DirectSight.Parser.Analysis.LineCoverage;
 12
 13namespace DirectSight.Parser;
 14
 15/// <summary>
 16/// Parser for XML reports generated by OpenCover.
 17/// </summary>
 18internal class OpenCoverParser : ParserBase
 19{
 20    /// <summary>
 21    /// Regex to analyze if a class name represents a generic class.
 22    /// </summary>
 123    private static readonly Regex GenericClassRegex =
 124        new("<.*>$", RegexOptions.Compiled);
 25
 26    /// <summary>
 27    /// Regex to analyze if a class name represents an async (generic) class.
 28    /// Format gets generated by 'dotnet test --collect "Code Coverage;Format=Cobertura"'.
 29    /// </summary>
 130    private static readonly Regex AsyncClassRegex =
 131        new("^(?<ClassName>.+)\\.<.*>.*__(?:.+(?<GenericTypes><.+>))?", RegexOptions.Compiled);
 32
 33    /// <summary>
 34    /// Regex to analyze if a method name belongs to a lamda expression.
 35    /// </summary>
 136    private static readonly Regex LambdaMethodNameRegex =
 137        new("::<.+>.+__[^\\|]+$", RegexOptions.Compiled);
 38
 39    /// <summary>
 40    /// Regex to analyze if a method name is generated by compiler.
 41    /// </summary>
 142    private static readonly Regex CompilerGeneratedMethodNameRegex =
 143        new(@"<(?<CompilerGeneratedName>.+)>.+__.+::MoveNext\(\)$", RegexOptions.Compiled);
 44
 45    /// <summary>
 46    /// Regex to analyze if a method name is a nested method (a method nested within a method).
 47    /// </summary>
 148    private static readonly Regex LocalFunctionMethodNameRegex =
 149        new(@"^.*(?<ParentMethodName><.+>).*__(?<NestedMethodName>[^\|]+)\|.+\((?<Arguments>.*)\).*$", RegexOptions.Comp
 50
 51    /// <summary>
 52    /// Regex to extract short method name.
 53    /// </summary>
 154    private static readonly Regex MethodRegex =
 155        new(@"^.*::(?<MethodName>.+)\((?<Arguments>.*)\)$", RegexOptions.Compiled);
 56
 57    /// <summary>
 58    /// Cache for method names.
 59    /// </summary>
 160    private static readonly ConcurrentDictionary<string, string> MethodNameMap = new();
 61
 62    /// <summary>
 63    /// Initializes a new instance of the <see cref="OpenCoverParser" /> class.
 64    /// </summary>
 8865    internal OpenCoverParser()
 8866    {
 8867    }
 68
 69    /// <summary>
 70    /// Parses the given XML report.
 71    /// </summary>
 72    /// <param name="report">The XML report.</param>
 73    /// <returns>The parser result.</returns>
 74    public ParserResult Parse(XContainer report)
 8875    {
 8876        ArgumentNullException.ThrowIfNull(report);
 77
 8878        var assemblies = new List<Assembly>();
 79
 8880        var modules = report.Descendants("Module")
 110281            .Where(m => m.Attribute("skippedDueTo") == null)
 8882            .ToArray();
 8883        var files = report.Descendants("File").ToArray();
 84
 8885        var trackedMethods = new Dictionary<string, string>();
 86
 32487        foreach (var trackedMethodElement in report.Descendants("TrackedMethod"))
 3088        {
 3089            if (trackedMethods.ContainsKey(trackedMethodElement.Attribute("uid").Value))
 090            {
 091                ConsoleLogger.Warn(
 092                    "The 'uid' of tracked method '{0}' is not unique. Ignoring tracked methods.",
 093                    trackedMethodElement.Attribute("name").Value);
 94
 095                trackedMethods.Clear();
 96
 097                break;
 98            }
 99            else
 30100            {
 30101                trackedMethods.Add(trackedMethodElement.Attribute("uid").Value, trackedMethodElement.Attribute("name").V
 30102            }
 30103        }
 104
 88105        var assemblyNames = modules
 88106            .Select(m => m.Element("ModuleName").Value)
 88107            .Distinct()
 88108            .OrderBy(a => a)
 88109            .ToArray();
 110
 88111        var assemblyModules = assemblyNames
 88112            .ToDictionary(
 88113                k => k,
 264114                v => modules.Where(t => t.Element("ModuleName").Value.Equals(v)).ToArray());
 115
 440116        foreach (var assemblyName in assemblyNames)
 88117        {
 88118            var assembly = ProcessAssembly(assemblyModules, files, trackedMethods, assemblyName);
 88119            if (assembly.Classes.Any())
 88120            {
 88121                assemblies.Add(assembly);
 88122            }
 88123        }
 124
 176125        var result = new ParserResult([.. assemblies.OrderBy(a => a.Name)], true, this.ToString());
 88126        return result;
 88127    }
 128
 129    /// <summary>
 130    /// Processes the given assembly.
 131    /// </summary>
 132    /// <param name="assemblyModules">The modules belonging to a assembly name.</param>
 133    /// <param name="files">The files.</param>
 134    /// <param name="trackedMethods">The tracked methods.</param>
 135    /// <param name="assemblyName">Name of the assembly.</param>
 136    /// <returns>The <see cref="Assembly"/>.</returns>
 137    private static Assembly ProcessAssembly(
 138        Dictionary<string, XElement[]> assemblyModules,
 139        XElement[] files,
 140        IDictionary<string, string> trackedMethods,
 141        string assemblyName)
 88142    {
 88143        ConsoleLogger.Debug("Current Assembly: {0}", assemblyName);
 144
 88145        var fileIdsByFilename = assemblyModules[assemblyName]
 88146            .Elements("Files")
 88147            .Elements("File")
 1272148            .GroupBy(f => f.Attribute("fullPath").Value)
 2632149            .ToDictionary(g => g.Key, g => new FileElement(g));
 150
 88151        var classNames = assemblyModules[assemblyName]
 88152            .Elements("Classes")
 88153            .Elements("Class")
 2222154            .Where(c => c.Attribute("skippedDueTo") == null)
 2149155            .Where(c => !c.Element("FullName").Value.Contains("<>f__AnonymousType"))
 2149156            .Where(c => c.Element("Methods").Elements("Method").Any())
 88157            .Select(c =>
 1973158                {
 1973159                    string fullname = c.Element("FullName").Value;
 1973160                    int nestedClassSeparatorIndex = fullname.IndexOf('/');
 1973161                    if (nestedClassSeparatorIndex > -1)
 504162                    {
 504163                        string className = fullname.Substring(0, nestedClassSeparatorIndex);
 504164                        return Tuple.Create(className, className);
 88165                    }
 88166
 1469167                    if (fullname.Contains('<'))
 0168                    {
 0169                        var match = AsyncClassRegex.Match(fullname);
 88170
 0171                        if (match.Success)
 0172                        {
 0173                            return Tuple.Create(
 0174                                match.Groups["ClassName"].Value,
 0175                                match.Groups["ClassName"].Value + match.Groups["GenericTypes"].Value);
 88176                        }
 0177                    }
 88178
 1469179                    return Tuple.Create(fullname, fullname);
 1973180                })
 1973181            .Where(c => !c.Item1.Contains('<') || GenericClassRegex.IsMatch(c.Item1))
 1973182            .Select(i => i.Item1)
 88183            .Distinct()
 1469184            .OrderBy(name => name)
 88185            .ToArray();
 186
 88187        var assembly = new Assembly(assemblyName);
 188
 1557189        Parallel.ForEach(classNames, className => ProcessClass(assemblyModules, files, trackedMethods, fileIdsByFilename
 190
 88191        return assembly;
 88192    }
 193
 194    /// <summary>
 195    /// Processes the given class.
 196    /// </summary>
 197    /// <param name="assemblyModules">The modules belonging to a assembly name.</param>
 198    /// <param name="files">The files.</param>
 199    /// <param name="trackedMethods">The tracked methods.</param>
 200    /// <param name="fileIdsByFilename">Dictionary containing the file ids by filename.</param>
 201    /// <param name="assembly">The assembly.</param>
 202    /// <param name="className">Name of the class.</param>
 203    private static void ProcessClass(
 204        Dictionary<string, XElement[]> assemblyModules,
 205        XElement[] files,
 206        IDictionary<string, string> trackedMethods,
 207        Dictionary<string, FileElement> fileIdsByFilename,
 208        Assembly assembly,
 209        string className)
 1469210    {
 1469211        var methods = assemblyModules[assembly.Name]
 1469212            .Elements("Classes")
 1469213            .Elements("Class")
 37534214            .Where(c => c.Element("FullName").Value.Equals(className)
 37534215                        || c.Element("FullName").Value.StartsWith(className + "/", StringComparison.Ordinal)
 37534216                        || c.Element("FullName").Value.StartsWith(className + ".", StringComparison.Ordinal))
 1469217            .Elements("Methods")
 1469218            .Elements("Method")
 6857219            .Where(m => m.Attribute("skippedDueTo") == null)
 1469220            .ToArray();
 221
 1469222        var fileIdsOfClassInSequencePoints = methods
 1469223            .Elements("SequencePoints")
 1469224            .Elements("SequencePoint")
 17053225            .Select(seqpnt => seqpnt.Attribute("fileid")?.Value)
 17053226            .Where(seqpnt => seqpnt != null && seqpnt != "0")
 1469227            .ToArray();
 228
 1469229        var fileIdsOfClass = fileIdsOfClassInSequencePoints
 1469230            .Distinct()
 1469231            .ToHashSet();
 232
 1469233        var filesOfClass = files
 21555234            .Where(file => fileIdsOfClass.Contains(file.Attribute("uid").Value))
 1524235            .Select(file => file.Attribute("fullPath").Value)
 1469236            .Distinct()
 1469237            .ToArray();
 238
 1469239        var @class = new Class(className, assembly);
 240
 7455241        foreach (var file in filesOfClass)
 1524242        {
 1524243            @class.AddFile(ProcessFile(trackedMethods, fileIdsByFilename[file], file, methods));
 1524244        }
 245
 1469246        assembly.AddClass(@class);
 1469247    }
 248
 249    /// <summary>
 250    /// Processes the file.
 251    /// </summary>
 252    /// <param name="trackedMethods">The tracked methods.</param>
 253    /// <param name="fileIds">The file ids of the class.</param>
 254    /// <param name="filePath">The file path.</param>
 255    /// <param name="methods">The methods.</param>
 256    /// <returns>The <see cref="CodeFile"/>.</returns>
 257    private static CodeFile ProcessFile(IDictionary<string, string> trackedMethods, FileElement fileIds, string filePath
 1524258    {
 1524259        var seqpntsOfFile = methods
 1524260            .Elements("SequencePoints")
 1524261            .Elements("SequencePoint")
 19575262            .Where(seqpnt => (seqpnt.Attribute("fileid") != null
 19575263                                && fileIds.Uids.Contains(seqpnt.Attribute("fileid").Value))
 19575264                || (seqpnt.Attribute("fileid") == null && seqpnt.Parent.Parent.Element("FileRef") != null
 19575265                    && fileIds.Uids.Contains(seqpnt.Parent.Parent.Element("FileRef").Attribute("uid").Value)))
 17053266            .Select(seqpnt => new
 17053267            {
 17053268                LineNumberStart = int.Parse(seqpnt.Attribute("sl").Value),
 17053269                LineNumberEnd =
 17053270                    seqpnt.Attribute("el") != null
 17053271                    ? int.Parse(seqpnt.Attribute("el").Value)
 17053272                    : int.Parse(seqpnt.Attribute("sl").Value),
 17053273                Visits = seqpnt.Attribute("vc").Value.ParseLargeInteger(),
 17053274                TrackedMethodRefs = seqpnt.Elements("TrackedMethodRefs")
 17053275                    .Elements("TrackedMethodRef")
 7950276                    .Select(t => new
 7950277                    {
 7950278                        Visits = t.Attribute("vc").Value.ParseLargeInteger(),
 7950279                        TrackedMethodId = t.Attribute("uid").Value
 7950280                    })
 17053281            })
 17053282            .OrderBy(seqpnt => seqpnt.LineNumberEnd)
 1524283            .ToArray();
 284
 1524285        var branches = GetBranches(methods, fileIds);
 286
 1524287        var coverageByTrackedMethod = seqpntsOfFile
 17053288            .SelectMany(s => s.TrackedMethodRefs)
 3930289            .Select(t => t.TrackedMethodId)
 1524290            .Distinct()
 1524291            .ToDictionary(
 450292                id => id,
 1974293                id => new CoverageByTrackedMethod
 1974294                {
 1974295                    Coverage = LineInfoFactory.Create(0, -1),
 1974296                    LineVisitStatus = LineInfoFactory.Create(0, LineVisitStatus.NotCoverable)
 1974297                });
 298
 1524299        var coverage = LineInfoFactory.Create(0, -1);
 1524300        var lineVisitStatus = LineInfoFactory.Create(0, LineVisitStatus.NotCoverable);
 301
 1524302        if (seqpntsOfFile.Length > 0)
 1524303        {
 1524304            coverage = LineInfoFactory.Create(seqpntsOfFile[seqpntsOfFile.LongLength - 1].LineNumberEnd + 1, -1);
 1524305            lineVisitStatus = LineInfoFactory.Create(seqpntsOfFile[seqpntsOfFile.LongLength - 1].LineNumberEnd + 1, Line
 306
 5472307            foreach (var trackedMethodCoverage in coverageByTrackedMethod)
 450308            {
 450309                trackedMethodCoverage.Value.Coverage = coverage.Clone();
 450310                trackedMethodCoverage.Value.LineVisitStatus = lineVisitStatus.Clone();
 450311            }
 312
 38678313            foreach (var seqpnt in seqpntsOfFile)
 17053314            {
 69268315                for (int lineNumber = seqpnt.LineNumberStart; lineNumber <= seqpnt.LineNumberEnd; lineNumber++)
 17581316                {
 17581317                    int visits = coverage[lineNumber] == -1 ? seqpnt.Visits : coverage[lineNumber] + seqpnt.Visits;
 17581318                    coverage[lineNumber] = visits;
 319
 17581320                    if (lineVisitStatus[lineNumber] != LineVisitStatus.Covered)
 17153321                    {
 17153322                        bool partiallyCovered = false;
 323
 324                        // Use 'LineNumberStart' instead of 'lineNumber' here. Branches have line number of first line o
 17153325                        if (branches.TryGetValue(seqpnt.LineNumberStart, out ICollection<Branch> branchesOfLine))
 452326                        {
 1350327                            partiallyCovered = branchesOfLine.Any(b => b.BranchVisits == 0);
 452328                        }
 329
 17153330                        LineVisitStatus statusOfLine =
 17153331                            visits > 0
 17153332                            ? (partiallyCovered ? LineVisitStatus.PartiallyCovered : LineVisitStatus.Covered)
 17153333                            : LineVisitStatus.NotCovered;
 334
 17153335                        lineVisitStatus[lineNumber] = (LineVisitStatus)Math.Max((int)lineVisitStatus[lineNumber], (int)s
 17153336                    }
 337
 17581338                    if (visits > -1)
 17581339                    {
 64203340                        foreach (var trackedMethodCoverage in coverageByTrackedMethod)
 5730341                        {
 5730342                            if (trackedMethodCoverage.Value.Coverage[lineNumber] == -1)
 5280343                            {
 5280344                                trackedMethodCoverage.Value.Coverage[lineNumber] = 0;
 5280345                                trackedMethodCoverage.Value.LineVisitStatus[lineNumber] = LineVisitStatus.NotCovered;
 5280346                            }
 5730347                        }
 17581348                    }
 349
 60783350                    foreach (var trackedMethod in seqpnt.TrackedMethodRefs)
 4020351                    {
 4020352                        var trackedMethodCoverage = coverageByTrackedMethod[trackedMethod.TrackedMethodId];
 353
 4020354                        int trackeMethodVisits =
 4020355                            trackedMethodCoverage.Coverage[lineNumber] == -1
 4020356                            ? trackedMethod.Visits
 4020357                            : trackedMethodCoverage.Coverage[lineNumber] + trackedMethod.Visits;
 358
 4020359                        LineVisitStatus statusOfLine =
 4020360                            trackeMethodVisits > 0
 4020361                            ? (LineVisitStatus)Math.Min((int)LineVisitStatus.Covered, (int)lineVisitStatus[lineNumber])
 4020362                            : LineVisitStatus.NotCovered;
 363
 4020364                        trackedMethodCoverage.Coverage[lineNumber] = trackeMethodVisits;
 4020365                        trackedMethodCoverage.LineVisitStatus[lineNumber] = statusOfLine;
 4020366                    }
 17581367                }
 17053368            }
 1524369        }
 370
 1524371        var codeFile = new CodeFile(filePath, coverage, lineVisitStatus, branches);
 372
 5472373        foreach (var trackedMethodCoverage in coverageByTrackedMethod)
 450374        {
 375            // Sometimes no corresponding MethodRef element exists
 450376            if (trackedMethods.TryGetValue(trackedMethodCoverage.Key, out string name))
 450377            {
 450378                string shortName = name[(name[..(name.IndexOf(':') + 1)].LastIndexOf('.') + 1)..];
 450379                var testMethod = new TestMethod(name, shortName);
 450380                codeFile.AddCoverageByTestMethod(testMethod, trackedMethodCoverage.Value);
 450381            }
 450382        }
 383
 1524384        var methodsOfFile = methods
 7595385            .Where(m => m.Element("FileRef") != null && fileIds.Uids.Contains(m.Element("FileRef").Attribute("uid").Valu
 1524386            .ToArray();
 387
 1524388        SetMethodMetrics(codeFile, methodsOfFile);
 1524389        SetCodeElements(codeFile, methodsOfFile);
 390
 1524391        return codeFile;
 1524392    }
 393
 394    /// <summary>
 395    /// Extracts the metrics from the given <see cref="XElement">XElements</see>.
 396    /// </summary>
 397    /// <param name="codeFile">The code file.</param>
 398    /// <param name="methodsOfFile">The methods of the file.</param>
 399    private static void SetMethodMetrics(CodeFile codeFile, IEnumerable<XElement> methodsOfFile)
 1524400    {
 18369401        foreach (var methodGroup in methodsOfFile.GroupBy(m => m.Element("Name").Value))
 4599402        {
 4599403            var method = methodGroup.First();
 404
 405            // Exclude properties and lambda expressions
 4599406            if (method.Attribute("skippedDueTo") != null
 4599407                || method.HasAttributeWithValue("isGetter", "true")
 4599408                || method.HasAttributeWithValue("isSetter", "true")
 4599409                || LambdaMethodNameRegex.IsMatch(methodGroup.Key))
 1612410            {
 1612411                continue;
 412            }
 413
 2987414            var metrics = new List<Metric>()
 2987415            {
 2987416                Metric.CyclomaticComplexity(
 2987417                    methodGroup.Max(m => int.Parse(m.Attribute("cyclomaticComplexity").Value))),
 2987418                Metric.SequenceCoverage(
 2987419                    methodGroup.Max(m => decimal.Parse(m.Attribute("sequenceCoverage").Value))),
 2987420                Metric.BranchCoverage(
 2987421                    methodGroup.Max(m => decimal.Parse(m.Attribute("branchCoverage").Value)))
 2987422            };
 423
 8961424            var npathComplexityAttributes = methodGroup.Select(m => m.Attribute("nPathComplexity")).Where(a => a != null
 425
 2987426            if (npathComplexityAttributes.Length > 0)
 2885427            {
 2885428                metrics.Insert(
 2885429                    1,
 2885430                    Metric.NPathComplexity(
 8655431                        npathComplexityAttributes.Select(a => int.Parse(a.Value)).Max(a => a < 0 ? int.MaxValue : a)));
 2885432            }
 433
 8961434            var crapScoreAttributes = methodGroup.Select(m => m.Attribute("crapScore")).Where(a => a != null).ToArray();
 2987435            if (crapScoreAttributes.Length > 0)
 2885436            {
 2885437                metrics.Add(Metric.CrapScore(
 5770438                    crapScoreAttributes.Max(a => decimal.Parse(a.Value))));
 2885439            }
 440
 2987441            string fullName = ExtractMethodName(methodGroup.Key);
 2987442            string shortName = MethodRegex.Replace(
 2987443                fullName,
 5810444                m => string.Format(
 5810445                    "{0}({1})",
 5810446                    m.Groups["MethodName"].Value,
 5810447                    m.Groups["Arguments"].Value.Length > 0 ? "..." : string.Empty));
 448
 2987449            var methodMetric = new MethodMetric(fullName, shortName, metrics);
 450
 2987451            var seqpnt = method
 2987452                .Elements("SequencePoints")
 2987453                .Elements("SequencePoint")
 2987454                .FirstOrDefault();
 455
 2987456            if (seqpnt != null)
 2987457            {
 2987458                methodMetric.Line = int.Parse(seqpnt.Attribute("sl").Value);
 2987459            }
 460
 2987461            codeFile.AddMethodMetric(methodMetric);
 2987462        }
 1524463    }
 464
 465    /// <summary>
 466    /// Gets the branches by line number.
 467    /// </summary>
 468    /// <param name="methods">The methods.</param>
 469    /// <param name="fileIds">The file ids of the class.</param>
 470    /// <returns>The branches by line number.</returns>
 471    private static Dictionary<int, ICollection<Branch>> GetBranches(XElement[] methods, FileElement fileIds)
 1524472    {
 1524473        var branchPoints = methods
 1524474            .Elements("BranchPoints")
 1524475            .Elements("BranchPoint")
 1524476            .ToArray();
 477
 478        // OpenCover supports this since version 4.5.3207
 1524479        if (branchPoints.Length == 0 || branchPoints[0].Attribute("sl") == null)
 1090480        {
 1090481            return [];
 482        }
 483
 434484        var result = new Dictionary<int, Dictionary<string, Branch>>();
 3110485        foreach (var branchPoint in branchPoints)
 904486        {
 904487            if (branchPoint.Attribute("fileid") != null
 904488                && !fileIds.Uids.Contains(branchPoint.Attribute("fileid").Value))
 164489            {
 490                // If fileid is available, verify that branch belongs to same file (available since version OpenCover.4.
 164491                continue;
 492            }
 493
 740494            int lineNumber = int.Parse(branchPoint.Attribute("sl").Value);
 495
 740496            string identifier = string.Format(
 740497                "{0}_{1}_{2}_{3}",
 740498                lineNumber,
 740499                branchPoint.Attribute("path").Value,
 740500                branchPoint.Attribute("offset").Value,
 740501                branchPoint.Attribute("offsetend").Value);
 740502            int vc = branchPoint.Attribute("vc").Value.ParseLargeInteger();
 503
 740504            if (result.TryGetValue(lineNumber, out var branches))
 376505            {
 376506                if (branches.TryGetValue(identifier, out var found))
 0507                {
 0508                    found.BranchVisits += vc;
 0509                }
 510                else
 376511                {
 376512                    branches.Add(identifier, new Branch(vc, identifier));
 376513                }
 376514            }
 515            else
 364516            {
 364517                branches = new Dictionary<string, Branch>
 364518                {
 364519                    { identifier, new Branch(vc, identifier) }
 364520                };
 364521                result.Add(lineNumber, branches);
 364522            }
 740523        }
 524
 1162525        return result.ToDictionary(k => k.Key, v => (ICollection<Branch>)[.. v.Value.Values]);
 1524526    }
 527
 528    /// <summary>
 529    /// Extracts the methods/properties of the given <see cref="XElement">XElements</see>.
 530    /// </summary>
 531    /// <param name="codeFile">The code file.</param>
 532    /// <param name="methodsOfFile">The methods of the file.</param>
 533    private static void SetCodeElements(CodeFile codeFile, IEnumerable<XElement> methodsOfFile)
 1524534    {
 13770535        foreach (var method in methodsOfFile)
 4599536        {
 4599537            if (method.Attribute("skippedDueTo") != null
 4599538                || LambdaMethodNameRegex.IsMatch(method.Element("Name").Value))
 176539            {
 176540                continue;
 541            }
 542
 4423543            string fullName = ExtractMethodName(method.Element("Name").Value);
 4423544            string methodName = fullName[(fullName.LastIndexOf(':') + 1)..];
 545
 4423546            CodeElementType type = CodeElementType.Method;
 547
 4423548            if (method.HasAttributeWithValue("isGetter", "true")
 4423549                || method.HasAttributeWithValue("isSetter", "true"))
 1436550            {
 1436551                type = CodeElementType.Property;
 1436552                methodName = methodName[4..];
 1436553            }
 554
 4423555            var seqpnts = method
 4423556                .Elements("SequencePoints")
 4423557                .Elements("SequencePoint")
 16877558                .Select(seqpnt => new
 16877559                {
 16877560                    LineNumberStart = int.Parse(seqpnt.Attribute("sl").Value),
 16877561                    LineNumberEnd =
 16877562                        seqpnt.Attribute("el") != null
 16877563                        ? int.Parse(seqpnt.Attribute("el").Value)
 16877564                        : int.Parse(seqpnt.Attribute("sl").Value)
 16877565                })
 4423566                .ToArray();
 567
 4423568            if (seqpnts.Length > 0)
 4423569            {
 21300570                int firstLine = seqpnts.Min(s => s.LineNumberStart);
 21300571                int lastLine = seqpnts.Max(s => s.LineNumberEnd);
 572
 4423573                codeFile.AddCodeElement(new CodeElement(
 4423574                    fullName,
 4423575                    methodName,
 4423576                    type,
 4423577                    firstLine,
 4423578                    lastLine,
 4423579                    codeFile.CoverageQuotaInRange(firstLine, lastLine)));
 4423580            }
 4423581        }
 1524582    }
 583
 584    /// <summary>
 585    /// Extracts the method name. For async methods the original name is returned.
 586    /// </summary>
 587    /// <param name="methodName">The full method name.</param>
 588    /// <returns>The method name.</returns>
 589    private static string ExtractMethodName(string methodName)
 7410590    {
 7410591        if (!MethodNameMap.TryGetValue(methodName, out var fullName))
 381592        {
 381593            if (methodName.Contains("|"))
 0594            {
 0595                Match match = LocalFunctionMethodNameRegex.Match(methodName);
 596
 0597                if (match.Success)
 0598                {
 0599                    methodName = match.Groups["NestedMethodName"].Value + "(" + match.Groups["Arguments"].Value + ")";
 0600                }
 0601            }
 381602            else if (methodName.EndsWith("::MoveNext()"))
 328603            {
 328604                Match match = CompilerGeneratedMethodNameRegex.Match(methodName);
 605
 328606                if (match.Success)
 328607                {
 328608                    methodName = match.Groups["CompilerGeneratedName"].Value + "()";
 328609                }
 328610            }
 611
 381612            fullName = methodName;
 381613            MethodNameMap.TryAdd(methodName, fullName);
 381614        }
 615
 7410616        return fullName;
 7410617    }
 618
 619    private class FileElement
 620    {
 621        /// <summary>
 622        /// Initializes a new instance of the <see cref="FileElement" /> class.
 623        /// </summary>
 624        /// <param name="elements">The File elements.</param>
 1272625        public FileElement(IEnumerable<XElement> elements)
 1272626        {
 2544627            this.Uids = [.. elements.Select(f => f.Attribute("uid").Value)];
 1272628        }
 629
 630        /// <summary>
 631        /// Gets the uids.
 632        /// </summary>
 25932633        public HashSet<string> Uids { get; }
 634    }
 635}