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 27 string makeDiffView(ref Database db, const ConfigReport conf, ref Diff diff, 28 from.my.path.AbsolutePath workdir) @trusted { 29 auto doc = tmplBasicPage.filesCss; 30 doc.title("Diff View"); 31 32 toHtml(reportDiff(db, diff, workdir), doc.mainBody); 33 34 return doc.toPrettyString; 35 } 36 37 private: 38 39 void toHtml(DiffReport report, Element root) { 40 import std.array : array; 41 import std.conv : to; 42 import std.path : buildPath; 43 import std.range : repeat; 44 import arsd.dom : RawSource; 45 import dextool.plugin.mutate.backend.mutation_type : toUser; 46 47 void renderRawDiff(Element root, Diff.Line[] lines) { 48 import std.algorithm : countUntil, max; 49 import std.string : strip; 50 import std.uni : isWhite; 51 52 auto hunk = root.addChild("p"); 53 uint prev = lines.length != 0 ? lines[0].line : 0; 54 55 foreach (line; lines) { 56 if (line.line > prev + 1) { 57 hunk = root.addChild("p"); 58 } 59 60 auto s = hunk.addChild("span"); 61 auto begin = 0; 62 const first_ch = line.text.length != 0 ? line.text[0] : typeof(line.text[0]).init; 63 switch (first_ch) { 64 case '+': 65 s.setAttribute("class", "diff_add"); 66 begin = 1; 67 break; 68 case '-': 69 s.setAttribute("class", "diff_del"); 70 begin = 1; 71 break; 72 default: 73 } 74 75 auto txt = line.text[begin .. $]; 76 const spaces = max(0, txt.countUntil!(a => !a.isWhite) - begin); 77 s.addChild(new RawSource(root.parentDocument, format("%s:%s%-(%s%)", 78 line.line, first_ch, " ".repeat(spaces)))); 79 s.appendText(txt.strip); 80 s.addChild("br"); 81 82 prev = line.line; 83 } 84 } 85 86 void renderFiles() { 87 import std.algorithm : sort, map; 88 import std.typecons : tuple; 89 90 with (root.addChild("p")) { 91 appendText("This are the mutants for the modified lines."); 92 appendText(" "); 93 addChild("span", "Red").addClass("diff_del"); 94 appendText(" removed line."); 95 appendText(" "); 96 addChild("span", "Green").addClass("diff_add"); 97 appendText(" added line."); 98 } 99 100 auto tbl = tmplDefaultTable(root, ["Analyzed Diff", "Alive", "Killed"]); 101 102 foreach (const pkv; report.files 103 .byKeyValue 104 .map!(a => tuple(a.key, a.value.dup)) 105 .array 106 .sort!((a, b) => a[1] < b[1])) { 107 const path = report.files[pkv[0]]; 108 tbl.appendRow(tbl.td(path).setAttribute("colspan", "3") 109 .setAttribute("style", "vertical-align:top")); 110 111 auto r = tbl.appendRow(); 112 113 if (auto v = pkv[0] in report.rawDiff) 114 renderRawDiff(r.addChild("td"), *v); 115 else 116 continue; 117 118 auto alive_ids = r.addChild("td").setAttribute("style", "vertical-align:top"); 119 if (auto alive = pkv[0] in report.alive) { 120 foreach (a; (*alive).dup.sort!((a, b) => a.sloc.line < b.sloc.line)) { 121 auto link = alive_ids.addChild("a", format("%s:%s", 122 a.kind.toUser, a.sloc.line)); 123 link.href = format("%s#%s", buildPath(Html.fileDir, 124 pathToHtmlLink(path)), a.stId); 125 alive_ids.appendText(" "); 126 } 127 } 128 129 auto killed_ids = r.addChild("td").setAttribute("style", "vertical-align:top"); 130 if (auto killed = pkv[0] in report.killed) { 131 foreach (a; (*killed).dup.sort!((a, b) => a.sloc.line < b.sloc.line)) { 132 auto link = killed_ids.addChild("a", format("%s:%s", 133 a.kind.toUser, a.sloc.line)); 134 link.href = format("%s#%s", buildPath(Html.fileDir, 135 pathToHtmlLink(path)), a.stId); 136 killed_ids.appendText(" "); 137 } 138 } 139 } 140 } 141 142 void renderTestCases() { 143 root.addChild("p", "These are the test cases that killed mutants in the code changes.") 144 .appendText(format("%s test case(s) affected by the change", report.testCases.length)); 145 146 auto tc_tbl = tmplDefaultTable(root, ["Test Case"]); 147 foreach (tc; report.testCases) { 148 tc_tbl.appendRow(tc.name); 149 } 150 } 151 152 root.addChild("h2", "Diff View"); 153 root.addChild("p").appendHtml(format("Mutation Score <b>%.3s</b>", report.score)); 154 155 root.addChild("h3", "File(s) Report"); 156 renderFiles(); 157 158 root.addChild("h3", "Test Case(s) Report"); 159 renderTestCases(); 160 }