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.algorithm : countUntil, max, filter; 14 import std.array : appender, array, empty; 15 import std.conv : to; 16 import std.format : format; 17 import std.path : buildPath; 18 import std.range : repeat; 19 import std.string : strip; 20 import std.typecons : Tuple; 21 import std.uni : isWhite; 22 23 import arsd.dom : Document, Element, require, Table, RawSource; 24 import dextool.from; 25 26 import dextool.plugin.mutate.backend.database : Database; 27 import dextool.plugin.mutate.backend.diff_parser : Diff; 28 import dextool.plugin.mutate.backend.report.analyzers : DiffReport, reportDiff; 29 import dextool.plugin.mutate.backend.report.html.constants; 30 import dextool.plugin.mutate.backend.report.html.page_files : pathToHtmlLink; 31 import dextool.plugin.mutate.backend.report.html.tmpl : tmplBasicPage, tmplDefaultTable; 32 import dextool.plugin.mutate.backend.type : Mutation; 33 import dextool.plugin.mutate.config : ConfigReport; 34 import dextool.plugin.mutate.type : MutationKind; 35 36 string makeDiffView(ref Database db, ref const ConfigReport conf, 37 const(MutationKind)[] humanReadableKinds, const(Mutation.Kind)[] kinds, 38 ref Diff diff, from.dextool.type.AbsolutePath workdir) @trusted { 39 import std.datetime : Clock; 40 41 auto doc = tmplBasicPage; 42 doc.title(format("Diff View %(%s %) %s", humanReadableKinds, Clock.currTime)); 43 44 toHtml(reportDiff(db, kinds, diff, workdir), doc.mainBody); 45 46 return doc.toPrettyString; 47 } 48 49 private: 50 51 void toHtml(DiffReport report, Element root) { 52 import dextool.plugin.mutate.backend.mutation_type : toUser; 53 54 void renderRawDiff(Element root, Diff.Line[] lines) { 55 56 alias Row = Tuple!(const(char), "prefix", string, "txt", uint, "line", long, "spaces"); 57 auto rows = appender!(Row[])(); 58 foreach (line; lines) { 59 if (line.text.empty) { 60 rows.put(Row(' ', null, line.line, 0)); 61 } else { 62 const prefix = line.text[0]; 63 auto txt = line.text[1 .. $]; 64 const spaces = max(0, txt.countUntil!(a => !a.isWhite)); 65 rows.put(Row(prefix, txt.strip, line.line, spaces)); 66 } 67 } 68 69 auto tbl = root.addChild("table").setAttribute("id", "locs"); 70 71 foreach (row; rows.data.filter!(a => a.prefix == '-')) { 72 auto line = tbl.addChild("tr").addChild("td"); 73 line.setAttribute("id", format("%s-%s", "loc", row.line)); 74 line.addClass("loc"); 75 line.addChild("span", format("%s:", row.line)).addClass("line_nr"); 76 // force a newline in the generated html to improve readability 77 tbl.appendText("\n"); 78 79 line.addChild(new RawSource(root.parentDocument, format("%-(%s%)", 80 " ".repeat(row.spaces)))); 81 82 auto d0 = line.addChild("div").setAttribute("style", "display: inline;"); 83 d0.addChild("span", row.txt); 84 if (row.prefix == '+') { 85 d0.addClass("diff_add"); 86 } 87 } 88 89 //auto hunk = root.addChild("p"); 90 //uint prev = lines.length != 0 ? lines[0].line : 0; 91 // 92 //foreach (line; lines) { 93 // if (line.line > prev + 1) { 94 // hunk = root.addChild("p"); 95 // } 96 // 97 // auto s = hunk.addChild("span"); 98 // auto begin = 0; 99 // const first_ch = line.text.length != 0 ? line.text[0] : typeof(line.text[0]).init; 100 // switch (first_ch) { 101 // case '+': 102 // s.setAttribute("class", "diff_add"); 103 // begin = 1; 104 // break; 105 // case '-': 106 // s.setAttribute("class", "diff_del"); 107 // begin = 1; 108 // break; 109 // default: 110 // } 111 // 112 // auto txt = line.text[begin .. $]; 113 // const spaces = max(0, txt.countUntil!(a => !a.isWhite) - begin); 114 // s.addChild(new RawSource(root.parentDocument, format("%s:%s%-(%s%)", 115 // line.line, first_ch, " ".repeat(spaces)))); 116 // s.appendText(txt.strip); 117 // s.addChild("br"); 118 // 119 // prev = line.line; 120 //} 121 } 122 123 void renderFiles() { 124 import std.algorithm : sort, map; 125 import std.typecons : tuple; 126 127 with (root.addChild("p")) { 128 appendText("This are the mutants for the modified lines."); 129 appendText(" "); 130 addChild("span", "Red").addClass("diff_del"); 131 appendText(" removed line."); 132 appendText(" "); 133 addChild("span", "Green").addClass("diff_add"); 134 appendText(" added line."); 135 } 136 137 auto tbl = tmplDefaultTable(root, ["Analyzed Diff", "Alive", "Killed"]); 138 139 foreach (const pkv; report.files 140 .byKeyValue 141 .map!(a => tuple(a.key, a.value.dup)) 142 .array 143 .sort!((a, b) => a[1] < b[1])) { 144 const path = report.files[pkv[0]]; 145 tbl.appendRow(tbl.td(path).setAttribute("colspan", "3") 146 .setAttribute("style", "vertical-align:top")); 147 148 auto r = tbl.appendRow(); 149 150 if (auto v = pkv[0] in report.rawDiff) 151 renderRawDiff(r.addChild("td"), *v); 152 else 153 continue; 154 155 auto alive_ids = r.addChild("td").setAttribute("style", "vertical-align:top"); 156 if (auto alive = pkv[0] in report.alive) { 157 foreach (a; (*alive).dup.sort!((a, b) => a.sloc.line < b.sloc.line)) { 158 auto link = alive_ids.addChild("a", format("%s:%s", 159 a.kind.toUser, a.sloc.line)); 160 link.href = format("%s#%s", buildPath(htmlFileDir, 161 pathToHtmlLink(path)), a.id); 162 alive_ids.appendText(" "); 163 } 164 } 165 166 auto killed_ids = r.addChild("td").setAttribute("style", "vertical-align:top"); 167 if (auto killed = pkv[0] in report.killed) { 168 foreach (a; (*killed).dup.sort!((a, b) => a.sloc.line < b.sloc.line)) { 169 auto link = killed_ids.addChild("a", format("%s:%s", 170 a.kind.toUser, a.sloc.line)); 171 link.href = format("%s#%s", buildPath(htmlFileDir, 172 pathToHtmlLink(path)), a.id); 173 killed_ids.appendText(" "); 174 } 175 } 176 } 177 } 178 179 void renderTestCases() { 180 root.addChild("p", "This are the test cases that killed mutants in the code changes.") 181 .appendText(format("%s test case(s) affected by the change", report.testCases.length)); 182 183 auto tc_tbl = tmplDefaultTable(root, ["Test Case"]); 184 foreach (tc; report.testCases) { 185 tc_tbl.appendRow(tc.name); 186 } 187 } 188 189 root.addChild("h2", "Diff View"); 190 root.addChild("p").appendHtml(format("Mutation Score <b>%.3s</b>", report.score)); 191 192 root.addChild("h3", "File(s) Report"); 193 renderFiles(); 194 195 root.addChild("h3", "Test Case(s) Report"); 196 renderTestCases(); 197 }