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.json;
11 
12 import std.exception : collectException;
13 import logger = std.experimental.logger;
14 
15 import dextool.type;
16 
17 import dextool.plugin.mutate.backend.database : Database, IterateMutantRow;
18 import dextool.plugin.mutate.backend.interface_ : FilesysIO;
19 import dextool.plugin.mutate.backend.type : Mutation;
20 import dextool.plugin.mutate.config : ConfigReport;
21 import dextool.plugin.mutate.type : MutationKind, ReportSection;
22 
23 import dextool.plugin.mutate.backend.report.type : ReportEvent;
24 import dextool.plugin.mutate.backend.report.utility : MakeMutationTextResult,
25     makeMutationText, toSections;
26 
27     @safe:
28     /**
29  * Expects locations to be grouped by file.
30  *
31  * TODO this is ugly. Use a JSON serializer instead.
32  */
33     @safe final class ReportJson : ReportEvent {
34         import std.array : array;
35         import std.algorithm : map, joiner;
36         import std.conv : to;
37         import std.format : format;
38         import std.json;
39         import dextool.set;
40 
41         const Mutation.Kind[] kinds;
42         Set!ReportSection sections;
43         FilesysIO fio;
44 
45         JSONValue report;
46         JSONValue current_file;
47 
48         Path last_file;
49 
50         this(const Mutation.Kind[] kinds, const ConfigReport conf, FilesysIO fio) {
51             this.kinds = kinds;
52             this.fio = fio;
53 
54             sections = (conf.reportSection.length == 0
55                     ? conf.reportLevel.toSections : conf.reportSection.dup).setFromList;
56         }
57 
58         override void mutationKindEvent(const MutationKind[] kinds) {
59             report = ["types" : kinds.map!(a => a.to!string).array, "files" : []];
60         }
61 
62         override void locationStartEvent() {
63         }
64 
65         override void locationEvent(const ref IterateMutantRow r) @trusted {
66             bool new_file;
67 
68             if (last_file.length == 0) {
69                 current_file = ["filename" : r.file, "checksum" : format("%x", r.fileChecksum)];
70                 new_file = true;
71             } else if (last_file != r.file) {
72                 report["files"].array ~= current_file;
73                 current_file = ["filename" : r.file, "checksum" : format("%x", r.fileChecksum)];
74                 new_file = true;
75             }
76 
77             auto appendMutant() {
78                 JSONValue m = ["id" : r.id.to!long];
79                 m.object["kind"] = r.mutation.kind.to!string;
80                 m.object["status"] = r.mutation.status.to!string;
81                 m.object["line"] = r.sloc.line;
82                 m.object["column"] = r.sloc.column;
83                 m.object["begin"] = r.mutationPoint.offset.begin;
84                 m.object["end"] = r.mutationPoint.offset.end;
85 
86                 try {
87                     MakeMutationTextResult mut_txt;
88                     auto abs_path = AbsolutePath(FileName(r.file), DirName(fio.getOutputDir));
89                     mut_txt = makeMutationText(fio.makeInput(abs_path),
90                             r.mutationPoint.offset, r.mutation.kind, r.lang);
91                     m.object["value"] = mut_txt.mutation;
92                 } catch (Exception e) {
93                     logger.warning(e.msg);
94                 }
95                 if (new_file) {
96                     last_file = r.file;
97                     current_file.object["mutants"] = JSONValue([m]);
98                 } else {
99                     current_file["mutants"].array ~= m;
100                 }
101             }
102 
103             if (sections.contains(ReportSection.all_mut) || sections.contains(ReportSection.alive)
104                     && r.mutation.status == Mutation.Status.alive
105                     || sections.contains(ReportSection.killed)
106                     && r.mutation.status == Mutation.Status.killed) {
107                 appendMutant;
108             }
109         }
110 
111         override void locationEndEvent() @trusted {
112             report["files"].array ~= current_file;
113         }
114 
115         override void locationStatEvent() {
116             import std.stdio : writeln;
117 
118             writeln(report.toJSON(true));
119         }
120 
121         override void statEvent(ref Database db) {
122         }
123     }