< Summary

Information
Class: DirectSight.Reporting.Builders.HtmlReportBuilder
Assembly DirectSight
File(s): /home/runner/work/DirectSight/DirectSight/DirectSight/Reporting/Builders/HtmlReportBuilderBase.cs
Line coverage
91%
Covered lines: 204
Uncovered lines: 20
Coverable lines: 224
Total lines: 330
Line coverage: 91%
Branch coverage
75%
Covered branches: 47
Total branches: 62
Branch coverage: 75.8%
Method coverage

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor()100%11100%
CreateClassReport(...)93.33%303097.11%
CreateSummaryReport(...)59.37%323285.08%
CreateTargetDirectory()100%11100%

File(s)

/home/runner/work/DirectSight/DirectSight/DirectSight/Reporting/Builders/HtmlReportBuilderBase.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using System.Linq;
 5using DirectSight.Parser.Analysis;
 6using DirectSight.Reporting.Builders.Rendering;
 7
 8namespace DirectSight.Reporting.Builders;
 9
 10/// <summary>
 11/// Implementation of <see cref="IReportBuilder"/> that uses <see cref="IHtmlRenderer"/> to create reports.
 12/// </summary>
 13internal class HtmlReportBuilder : IReportBuilder
 14{
 15    /// <summary>
 16    /// Dictionary containing the filenames of the class reports by class.
 17    /// </summary>
 318    protected readonly IDictionary<string, string> fileNameByClass = new Dictionary<string, string>();
 19
 20    /// <summary>
 21    /// Gets the report type.
 22    /// </summary>
 23    /// <value>
 24    /// The report type.
 25    /// </value>
 126    public string ReportType => "Html";
 27
 28    /// <summary>
 29    /// Gets or sets the report context.
 30    /// </summary>
 31    /// <value>
 32    /// The report context.
 33    /// </value>
 8834    public ReportContext ReportContext { get; set; }
 35
 36    /// <summary>
 37    /// Creates a class report.
 38    /// </summary>
 39    /// <param name="class">The class.</param>
 40    /// <param name="fileAnalyses">The file analyses that correspond to the class.</param>
 41    public void CreateClassReport(Class @class, IEnumerable<FileAnalysis> fileAnalyses)
 3642    {
 3643        ArgumentNullException.ThrowIfNull(@class);
 44
 3645        ArgumentNullException.ThrowIfNull(fileAnalyses);
 46
 3647        using var reportRenderer = new HtmlRenderer(
 3648            this.fileNameByClass,
 3649            false,
 3650            ["custom_adaptive.css", "custom_bluered.css"],
 3651            "custom.css");
 52
 3653        reportRenderer.BeginClassReport(this.CreateTargetDirectory(), @class.Assembly, @class.Name, @class.DisplayName, 
 54
 3655        reportRenderer.HeaderWithBackLink("Summary");
 56
 3657        var infoCardItems = new List<CardLineItem>()
 3658        {
 3659            new("Class:", @class.DisplayName, null, CardLineItemAlignment.Left),
 3660            new("Assembly", @class.Assembly.ShortName, null, CardLineItemAlignment.Left),
 3661            new("File(s):", @class.Files.Select(f => f.Path).ToArray())
 3662        };
 63
 3664        var infoCard = new Card(
 3665            "Information",
 3666            string.Empty,
 3667            null,
 3668            infoCardItems.ToArray());
 69
 3670        reportRenderer.Cards([infoCard]);
 71
 3672        var cards = new List<Card>()
 3673        {
 3674            new(
 3675                "Line coverage",
 3676                @class.CoverageQuota.HasValue ? $"{Math.Floor(@class.CoverageQuota.Value)}%" : "N/A",
 3677                @class.CoverageQuota,
 3678                new CardLineItem("Covered lines:", @class.CoveredLines.ToString(), null),
 3679                new CardLineItem("Uncovered lines:", (@class.CoverableLines - @class.CoveredLines).ToString(), null),
 3680                new CardLineItem("Coverable lines:", @class.CoverableLines.ToString(), null),
 3681                new CardLineItem("Total lines:", @class.TotalLines.GetValueOrDefault().ToString(), null),
 3682                new CardLineItem(
 3683                    "Line coverage:",
 3684                    @class.CoverageQuota.HasValue ? $"{@class.CoverageQuota.Value}%" : "N/A",
 3685                    @class.CoverageQuota.HasValue ? $"{@class.CoveredLines} of {@class.CoverableLines}" : "N/A"))
 3686        };
 87
 3688        if (@class.CoveredBranches.HasValue && @class.TotalBranches.HasValue)
 3689        {
 3690            cards.Add(new Card(
 3691                "Branch coverage",
 3692                @class.BranchCoverageQuota.HasValue ? $"{Math.Floor(@class.BranchCoverageQuota.Value)}%" : "N/A",
 3693                @class.BranchCoverageQuota,
 3694                new CardLineItem("Covered branches:", @class.CoveredBranches.GetValueOrDefault().ToString(), null),
 3695                new CardLineItem("Total branches:", @class.TotalBranches.GetValueOrDefault().ToString(), null),
 3696                new CardLineItem(
 3697                    "Branch coverage:",
 3698                    @class.BranchCoverageQuota.HasValue ? $"{@class.BranchCoverageQuota.Value}%" : "N/A",
 3699                    @class.BranchCoverageQuota.HasValue
 36100                    ? $"{@class.CoveredBranches.GetValueOrDefault()} of {@class.TotalBranches.GetValueOrDefault()}"
 36101                    : "N/A")));
 36102        }
 103
 36104        cards.Add(new Card("Method coverage"));
 105
 36106        reportRenderer.Cards(cards);
 107
 70108        if (@class.Files.Any(f => f.MethodMetrics.Any()))
 28109        {
 28110            reportRenderer.Header("Metrics");
 28111            reportRenderer.MetricsTable(@class);
 28112        }
 113
 36114        reportRenderer.Header("File(s)");
 115
 36116        if (fileAnalyses.Any())
 32117        {
 32118            int fileIndex = 0;
 168119            foreach (var fileAnalysis in fileAnalyses)
 36120            {
 36121                reportRenderer.File(fileAnalysis.Path);
 122
 36123                if (!string.IsNullOrEmpty(fileAnalysis.Error))
 0124                {
 0125                    reportRenderer.Paragraph(fileAnalysis.Error);
 0126                }
 127                else
 36128                {
 36129                    reportRenderer.BeginLineAnalysisTable([string.Empty, "#", "Line", string.Empty, "Line coverage"]);
 130
 2768131                    foreach (var line in fileAnalysis.Lines)
 1330132                    {
 1330133                        reportRenderer.LineAnalysis(fileIndex, line);
 1330134                    }
 135
 36136                    reportRenderer.FinishTable();
 36137                }
 138
 36139                fileIndex++;
 36140            }
 32141        }
 142        else
 4143        {
 4144            reportRenderer.Paragraph(
 4145                "No files found. " +
 4146                "This usually happens if a file isn't covered by a test or the class does not contain any sequence point
 4147                "(e.g. a class that only contains auto properties).");
 4148        }
 149
 36150        reportRenderer.AddFooter();
 151
 36152        if (fileAnalyses.Any())
 32153        {
 32154            var testMethods = @class.Files
 102155                .SelectMany(f => f.TestMethods)
 32156                .Distinct()
 188157                .OrderBy(l => l.ShortName);
 158
 32159            var codeElementsByFileIndex = new Dictionary<int, IEnumerable<CodeElement>>();
 160
 32161            int fileIndex = 0;
 168162            foreach (var file in @class.Files)
 36163            {
 146164                codeElementsByFileIndex.Add(fileIndex++, file.CodeElements.OrderBy(c => c.FirstLine));
 36165            }
 166
 32167            reportRenderer.TestMethods(testMethods, fileAnalyses, codeElementsByFileIndex);
 32168        }
 169
 36170        reportRenderer.SaveClassReport(this.CreateTargetDirectory(), @class.Name);
 72171    }
 172
 173    /// <summary>
 174    /// Creates the summary report.
 175    /// </summary>
 176    /// <param name="summaryResult">The summary result.</param>
 177    public void CreateSummaryReport(SummaryResult summaryResult)
 2178    {
 2179        ArgumentNullException.ThrowIfNull(summaryResult);
 180
 2181        using var reportRenderer = new HtmlRenderer(
 2182            this.fileNameByClass,
 2183            false,
 2184            ["custom_adaptive.css", "custom_bluered.css"],
 2185            "custom.css");
 186
 2187        string title = "Summary";
 188
 2189        reportRenderer.BeginSummaryReport(this.CreateTargetDirectory(), null, title);
 2190        reportRenderer.HeaderWithGithubLinks(title);
 191
 2192        var assembliesWithClasses = summaryResult.Assemblies
 2193            .Where(a => a.Classes.Any())
 2194            .ToArray();
 195
 2196        var infoCardItems = new List<CardLineItem>()
 2197        {
 2198            new("Parser:", summaryResult.UsedParser, null, CardLineItemAlignment.Left),
 2199            new("Assemblies:", assembliesWithClasses.Length.ToString(), null),
 2200            new("Classes:", assembliesWithClasses.SelectMany(a => a.Classes).Count().ToString(), null),
 38201            new("Files:", assembliesWithClasses.SelectMany(a => a.Classes).SelectMany(a => a.Files).Distinct().Count().T
 2202        };
 203
 2204        if (summaryResult.MinimumTimeStamp.HasValue || summaryResult.MaximumTimeStamp.HasValue)
 0205        {
 0206            infoCardItems.Add(new CardLineItem("Coverage date:", summaryResult.CoverageDate(), null, CardLineItemAlignme
 0207        }
 208
 2209        var cards = new List<Card>()
 2210        {
 2211            new(
 2212                "Information",
 2213                string.Empty,
 2214                null,
 2215                infoCardItems.ToArray()),
 2216            new(
 2217                "Line coverage",
 2218                summaryResult.CoverageQuota.HasValue ? $"{Math.Floor(summaryResult.CoverageQuota.Value)}%" : "N/A",
 2219                summaryResult.CoverageQuota,
 2220                new CardLineItem("Covered lines:", summaryResult.CoveredLines.ToString(), null),
 2221                new CardLineItem("Uncovered lines:", (summaryResult.CoverableLines - summaryResult.CoveredLines).ToStrin
 2222                new CardLineItem("Coverable lines:", summaryResult.CoverableLines.ToString(), null),
 2223                new CardLineItem("Total lines:", summaryResult.TotalLines.GetValueOrDefault().ToString(), null),
 2224                new CardLineItem(
 2225                    "Line coverage:",
 2226                    summaryResult.CoverageQuota.HasValue ? $"{summaryResult.CoverageQuota.Value}%" : "N/A",
 2227                    summaryResult.CoverageQuota.HasValue ? $"{summaryResult.CoveredLines} of {summaryResult.CoverableLin
 2228        };
 229
 2230        if (summaryResult.CoveredBranches.HasValue && summaryResult.TotalBranches.HasValue)
 2231        {
 2232            cards.Add(new Card(
 2233                "Branch coverage",
 2234                summaryResult.BranchCoverageQuota.HasValue ? $"{Math.Floor(summaryResult.BranchCoverageQuota.Value)}%" :
 2235                summaryResult.BranchCoverageQuota,
 2236                new CardLineItem("Covered branches:", summaryResult.CoveredBranches.GetValueOrDefault().ToString(), null
 2237                new CardLineItem("Total branches:", summaryResult.TotalBranches.GetValueOrDefault().ToString(), null),
 2238                new CardLineItem(
 2239                    "Branch coverage:",
 2240                    summaryResult.BranchCoverageQuota.HasValue ? $"{summaryResult.BranchCoverageQuota.Value}%" : "N/A",
 2241                    summaryResult.BranchCoverageQuota.HasValue
 2242                    ? $"{summaryResult.CoveredBranches.GetValueOrDefault()} of {summaryResult.TotalBranches.GetValueOrDe
 2243                    : "N/A")));
 2244        }
 245
 2246        cards.Add(new Card("Method coverage"));
 247
 2248        reportRenderer.Cards(cards);
 249
 2250        if (this.ReportContext.RiskHotspotAnalysisResult != null
 2251            && this.ReportContext.RiskHotspotAnalysisResult.CodeCodeQualityMetricsAvailable)
 2252        {
 2253            reportRenderer.Header("Risk Hotspots");
 254
 2255            if (this.ReportContext.RiskHotspotAnalysisResult.RiskHotspots.Count > 0)
 0256            {
 0257                reportRenderer.BeginRiskHotspots();
 0258                reportRenderer.RiskHotspots(this.ReportContext.RiskHotspotAnalysisResult.RiskHotspots);
 0259                reportRenderer.FinishRiskHotspots();
 0260            }
 261            else
 2262            {
 263                // Angular element has to be present
 2264                reportRenderer.BeginRiskHotspots();
 2265                reportRenderer.FinishRiskHotspots();
 266
 2267                reportRenderer.Paragraph("No risk hotspots found.");
 2268            }
 2269        }
 270        else
 0271        {
 272            // Angular element has to be present
 0273            reportRenderer.BeginRiskHotspots();
 0274            reportRenderer.FinishRiskHotspots();
 0275        }
 276
 2277        reportRenderer.Header("Coverage");
 278
 2279        if (assembliesWithClasses.Length != 0)
 2280        {
 2281            reportRenderer.BeginSummaryTable();
 2282            reportRenderer.BeginSummaryTable(summaryResult.SupportsBranchCoverage);
 283
 10284            foreach (var assembly in assembliesWithClasses)
 2285            {
 2286                reportRenderer.SummaryAssembly(assembly, summaryResult.SupportsBranchCoverage);
 287
 78288                foreach (var @class in assembly.Classes)
 36289                {
 36290                    reportRenderer.SummaryClass(@class, summaryResult.SupportsBranchCoverage);
 36291                }
 2292            }
 293
 2294            reportRenderer.FinishTable();
 2295            reportRenderer.FinishSummaryTable();
 2296        }
 297        else
 0298        {
 299            // Angular element has to be present
 0300            reportRenderer.BeginSummaryTable();
 0301            reportRenderer.FinishSummaryTable();
 302
 0303            reportRenderer.Paragraph("No assemblies have been covered.");
 0304        }
 305
 2306        reportRenderer.CustomSummary(
 2307            assembliesWithClasses,
 2308            this.ReportContext.RiskHotspotAnalysisResult.RiskHotspots,
 2309            summaryResult.SupportsBranchCoverage);
 310
 2311        reportRenderer.AddFooter();
 2312        reportRenderer.SaveSummaryReport(this.CreateTargetDirectory());
 313
 2314        string targetDirectory = this.CreateTargetDirectory();
 315
 2316        File.Copy(
 2317            Path.Combine(targetDirectory, "index.html"),
 2318            Path.Combine(targetDirectory, "index.htm"),
 2319            true);
 4320    }
 321
 322    /// <summary>
 323    /// Creates the target directory.
 324    /// </summary>
 325    /// <returns>The target directory.</returns>
 326    protected string CreateTargetDirectory()
 78327    {
 78328        return this.ReportContext.ReportConfiguration.TargetDirectory;
 78329    }
 330}