1 /** 2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 */ 10 module dextool.plugin.mutate.backend.report.html.page_diff; 11 12 import logger = std.experimental.logger; 13 import std.format : format; 14 15 import arsd.dom : Document, Element, require, Table; 16 import my.from_; 17 18 import dextool.plugin.mutate.backend.database : Database; 19 import dextool.plugin.mutate.backend.diff_parser : Diff; 20 import dextool.plugin.mutate.backend.report.analyzers : DiffReport, reportDiff; 21 import dextool.plugin.mutate.backend.report.html.constants; 22 import dextool.plugin.mutate.backend.report.html.utility : pathToHtmlLink; 23 import dextool.plugin.mutate.backend.report.html.tmpl : tmplBasicPage, filesCss, tmplDefaultTable; 24 import dextool.plugin.mutate.backend.type : Mutation; 25 import dextool.plugin.mutate.config : ConfigReport; 26 import dextool.plugin.mutate.type : MutationKind; 27 28 string makeDiffView(ref Database db, const ConfigReport conf, 29 const(MutationKind)[] humanReadableKinds, const(Mutation.Kind)[] kinds, 30 ref Diff diff, from.my.path.AbsolutePath workdir) @trusted { 31 import std.datetime : Clock; 32 33 auto doc = tmplBasicPage.filesCss; 34 doc.title(format("Diff View %(%s %) %s", humanReadableKinds, Clock.currTime)); 35 36 toHtml(reportDiff(db, kinds, diff, workdir), doc.mainBody); 37 38 return doc.toPrettyString; 39 } 40 41 private: 42 43 void toHtml(DiffReport report, Element root) { 44 import std.array : array; 45 import std.conv : to; 46 import std.path : buildPath; 47 import std.range : repeat; 48 import arsd.dom : RawSource; 49 import dextool.plugin.mutate.backend.mutation_type : toUser; 50 51 void renderRawDiff(Element root, Diff.Line[] lines) { 52 import std.algorithm : countUntil, max; 53 import std..string : strip; 54 import std.uni : isWhite; 55 56 auto hunk = root.addChild("p"); 57 uint prev = lines.length != 0 ? lines[0].line : 0; 58 59 foreach (line; lines) { 60 if (line.line > prev + 1) { 61 hunk = root.addChild("p"); 62 } 63 64 auto s = hunk.addChild("span"); 65 auto begin = 0; 66 const first_ch = line.text.length != 0 ? line.text[0] : typeof(line.text[0]).init; 67 switch (first_ch) { 68 case '+': 69 s.setAttribute("class", "diff_add"); 70 begin = 1; 71 break; 72 case '-': 73 s.setAttribute("class", "diff_del"); 74 begin = 1; 75 break; 76 default: 77 } 78 79 auto txt = line.text[begin .. $]; 80 const spaces = max(0, txt.countUntil!(a => !a.isWhite) - begin); 81 s.addChild(new RawSource(root.parentDocument, format("%s:%s%-(%s%)", 82 line.line, first_ch, " ".repeat(spaces)))); 83 s.appendText(txt.strip); 84 s.addChild("br"); 85 86 prev = line.line; 87 } 88 } 89 90 void renderFiles() { 91 import std.algorithm : sort, map; 92 import std.typecons : tuple; 93 94 with (root.addChild("p")) { 95 appendText("This are the mutants for the modified lines."); 96 appendText(" "); 97 addChild("span", "Red").addClass("diff_del"); 98 appendText(" removed line."); 99 appendText(" "); 100 addChild("span", "Green").addClass("diff_add"); 101 appendText(" added line."); 102 } 103 104 auto tbl = tmplDefaultTable(root, ["Analyzed Diff", "Alive", "Killed"]); 105 106 foreach (const pkv; report.files 107 .byKeyValue 108 .map!(a => tuple(a.key, a.value.dup)) 109 .array 110 .sort!((a, b) => a[1] < b[1])) { 111 const path = report.files[pkv[0]]; 112 tbl.appendRow(tbl.td(path).setAttribute("colspan", "3") 113 .setAttribute("style", "vertical-align:top")); 114 115 auto r = tbl.appendRow(); 116 117 if (auto v = pkv[0] in report.rawDiff) 118 renderRawDiff(r.addChild("td"), *v); 119 else 120 continue; 121 122 auto alive_ids = r.addChild("td").setAttribute("style", "vertical-align:top"); 123 if (auto alive = pkv[0] in report.alive) { 124 foreach (a; (*alive).dup.sort!((a, b) => a.sloc.line < b.sloc.line)) { 125 auto link = alive_ids.addChild("a", format("%s:%s", 126 a.kind.toUser, a.sloc.line)); 127 link.href = format("%s#%s", buildPath(Html.fileDir, 128 pathToHtmlLink(path)), a.id); 129 alive_ids.appendText(" "); 130 } 131 } 132 133 auto killed_ids = r.addChild("td").setAttribute("style", "vertical-align:top"); 134 if (auto killed = pkv[0] in report.killed) { 135 foreach (a; (*killed).dup.sort!((a, b) => a.sloc.line < b.sloc.line)) { 136 auto link = killed_ids.addChild("a", format("%s:%s", 137 a.kind.toUser, a.sloc.line)); 138 link.href = format("%s#%s", buildPath(Html.fileDir, 139 pathToHtmlLink(path)), a.id); 140 killed_ids.appendText(" "); 141 } 142 } 143 } 144 } 145 146 void renderTestCases() { 147 root.addChild("p", "This are the test cases that killed mutants in the code changes.") 148 .appendText(format("%s test case(s) affected by the change", report.testCases.length)); 149 150 auto tc_tbl = tmplDefaultTable(root, ["Test Case"]); 151 foreach (tc; report.testCases) { 152 tc_tbl.appendRow(tc.name); 153 } 154 } 155 156 root.addChild("h2", "Diff View"); 157 root.addChild("p").appendHtml(format("Mutation Score <b>%.3s</b>", report.score)); 158 159 root.addChild("h3", "File(s) Report"); 160 renderFiles(); 161 162 root.addChild("h3", "Test Case(s) Report"); 163 renderTestCases(); 164 }