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.compiler;
11 
12 import logger = std.experimental.logger;
13 import std.exception : collectException;
14 
15 import dextool.type;
16 
17 import dextool.plugin.mutate.backend.database : Database, IterateMutantRow;
18 import dextool.plugin.mutate.backend.generate_mutant : MakeMutationTextResult, makeMutationText;
19 import dextool.plugin.mutate.backend.interface_ : FilesysIO;
20 import dextool.plugin.mutate.backend.report.type : SimpleWriter, ReportEvent;
21 import dextool.plugin.mutate.backend.report.utility : window, windowSize;
22 import dextool.plugin.mutate.backend.type : Mutation;
23 import dextool.plugin.mutate.type : MutationKind, ReportLevel;
24 
25 /** Report mutations as gcc would do for compilation warnings with fixit hints.
26  *
27  * #SPC-report_for_tool_integration
28  */
29 @safe final class ReportCompiler : ReportEvent {
30     import std.algorithm : each;
31     import std.conv : to;
32     import std.format : format;
33     import dextool.plugin.mutate.backend.utility;
34 
35     const Mutation.Kind[] kinds;
36     const ReportLevel report_level;
37     FilesysIO fio;
38 
39     CompilerConsole!SimpleWriter compiler;
40 
41     this(Mutation.Kind[] kinds, ReportLevel report_level, FilesysIO fio) {
42         this.kinds = kinds;
43         this.report_level = report_level;
44         this.fio = fio;
45     }
46 
47     override void mutationKindEvent(const MutationKind[]) {
48         compiler = CompilerConsole!SimpleWriter(delegate(const(char)[] s) @trusted {
49             import std.stdio : stderr, write;
50 
51             stderr.write(s);
52         });
53     }
54 
55     override void locationStartEvent(ref Database db) {
56     }
57 
58     override void locationEvent(ref Database db, const ref IterateMutantRow r) @trusted {
59         import dextool.plugin.mutate.backend.generate_mutant : makeMutation;
60 
61         void report() {
62             AbsolutePath abs_path;
63             MakeMutationTextResult mut_txt;
64             try {
65                 abs_path = AbsolutePath(FileName(r.file), DirName(fio.getOutputDir));
66                 mut_txt = makeMutationText(fio.makeInput(abs_path),
67                         r.mutationPoint.offset, r.mutation.kind, r.lang);
68             } catch (Exception e) {
69                 logger.warning(e.msg);
70             }
71 
72             // dfmt off
73             auto b = compiler.build
74                 .file(abs_path)
75                 .start(r.sloc)
76                 .end(r.slocEnd)
77                 .begin("%s: replace '%s' with '%s'", r.mutation.kind.toUser,
78                        window(mut_txt.original, windowSize),
79                        window(mut_txt.mutation, windowSize))
80                 .note("status:%s id:%s", r.mutation.status, r.id);
81 
82             if (mut_txt.original.length > windowSize)
83                 b = b.note("replace '%s'", mut_txt.original);
84             if (mut_txt.mutation.length > windowSize)
85                 b = b.note("with '%s'", mut_txt.mutation);
86 
87             b.fixit(mut_txt.mutation).end;
88             // dfmt on
89         }
90 
91         try {
92             // summary is the default and according to the specification of the
93             // default for tool integration alive mutations shall be printed.
94             final switch (report_level) {
95             case ReportLevel.summary:
96                 break;
97             case ReportLevel.alive:
98                 if (r.mutation.status == Mutation.Status.alive) {
99                     report();
100                 }
101                 break;
102             case ReportLevel.all:
103                 report();
104                 break;
105             }
106         } catch (Exception e) {
107             logger.trace(e.msg).collectException;
108         }
109     }
110 
111     override void locationEndEvent() {
112     }
113 
114     override void locationStatEvent() {
115     }
116 
117     override void statEvent(ref Database db) {
118     }
119 }
120 
121 private:
122 
123 struct CompilerConsole(Writer) {
124     import std.ascii : newline;
125     import std.format : formattedWrite, format;
126     import std.range : put;
127 
128     private Writer w;
129 
130     this(Writer w) {
131         this.w = w;
132     }
133 
134     auto build() {
135         return CompilerMsgBuilder!Writer(w);
136     }
137 }
138 
139 /** Build a compiler msg that follows how GCC would print to the console.
140  *
141  * Note that it should write to stderr.
142  */
143 struct CompilerMsgBuilder(Writer) {
144     import std.ascii : newline;
145     import std.format : formattedWrite;
146     import std.range : put;
147     import dextool.plugin.mutate.backend.type : SourceLoc;
148 
149     private {
150         Writer w;
151         string file_;
152         SourceLoc start_;
153         SourceLoc end_;
154     }
155 
156     this(Writer w) {
157         this.w = w;
158     }
159 
160     auto file(string a) {
161         file_ = a;
162         return this;
163     }
164 
165     auto start(SourceLoc a) {
166         start_ = a;
167         return this;
168     }
169 
170     auto end(SourceLoc a) {
171         end_ = a;
172         return this;
173     }
174 
175     auto begin(ARGS...)(auto ref ARGS args) {
176         // for now the severity is hard coded to warning because nothing else
177         // needs to be supported.
178         formattedWrite(w, "%s:%s:%s: warning: ", file_, start_.line, start_.column);
179         formattedWrite(w, args);
180         put(w, newline);
181         return this;
182     }
183 
184     auto note(ARGS...)(auto ref ARGS args) {
185         formattedWrite(w, "%s:%s:%s: note: ", file_, start_.line, start_.column);
186         formattedWrite(w, args);
187         put(w, newline);
188         return this;
189     }
190 
191     auto fixit(const(char)[] mutation) {
192         // Example of a fixit hint from gcc:
193         // fix-it:"foo.cpp":{5:12-5:17}:"argc"
194         // the second value in the location is bytes (starting from 1) from the
195         // start of the line.
196         formattedWrite(w, `fix-it:"%s":{%s:%s-%s:%s}:"%s"`, file_, start_.line,
197                 start_.column, end_.line, end_.column, mutation);
198         put(w, newline);
199         return this;
200     }
201 
202     void end() {
203     }
204 }