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 }