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 }