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 }