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 }