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