• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Tint Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "src/diagnostic/formatter.h"
16 
17 #include <algorithm>
18 #include <iterator>
19 #include <vector>
20 
21 #include "src/diagnostic/diagnostic.h"
22 #include "src/diagnostic/printer.h"
23 
24 namespace tint {
25 namespace diag {
26 namespace {
27 
to_str(Severity severity)28 const char* to_str(Severity severity) {
29   switch (severity) {
30     case Severity::Note:
31       return "note";
32     case Severity::Warning:
33       return "warning";
34     case Severity::Error:
35       return "error";
36     case Severity::InternalCompilerError:
37       return "internal compiler error";
38     case Severity::Fatal:
39       return "fatal";
40   }
41   return "";
42 }
43 
to_str(const Source::Location & location)44 std::string to_str(const Source::Location& location) {
45   std::stringstream ss;
46   if (location.line > 0) {
47     ss << location.line;
48     if (location.column > 0) {
49       ss << ":" << location.column;
50     }
51   }
52   return ss.str();
53 }
54 
55 }  // namespace
56 
57 /// State holds the internal formatter state for a format() call.
58 struct Formatter::State {
59   /// Constructs a State associated with the given printer.
60   /// @param p the printer to write formatted messages to.
Statetint::diag::Formatter::State61   explicit State(Printer* p) : printer(p) {}
~Statetint::diag::Formatter::State62   ~State() { flush(); }
63 
64   /// set_style() sets the current style to new_style, flushing any pending
65   /// messages to the printer if the style changed.
66   /// @param new_style the new style to apply for future written messages.
set_styletint::diag::Formatter::State67   void set_style(const diag::Style& new_style) {
68     if (style.color != new_style.color || style.bold != new_style.bold) {
69       flush();
70       style = new_style;
71     }
72   }
73 
74   /// flush writes any pending messages to the printer, clearing the buffer.
flushtint::diag::Formatter::State75   void flush() {
76     auto str = stream.str();
77     if (str.length() > 0) {
78       printer->write(str, style);
79       std::stringstream reset;
80       stream.swap(reset);
81     }
82   }
83 
84   /// operator<< queues msg to be written to the printer.
85   /// @param msg the value or string to write to the printer
86   /// @returns this State so that calls can be chained
87   template <typename T>
operator <<tint::diag::Formatter::State88   State& operator<<(const T& msg) {
89     stream << msg;
90     return *this;
91   }
92 
93   /// newline queues a newline to be written to the printer.
newlinetint::diag::Formatter::State94   void newline() { stream << std::endl; }
95 
96   /// repeat queues the character c to be written to the printer n times.
97   /// @param c the character to print `n` times
98   /// @param n the number of times to print character `c`
repeattint::diag::Formatter::State99   void repeat(char c, size_t n) {
100     std::fill_n(std::ostream_iterator<char>(stream), n, c);
101   }
102 
103  private:
104   Printer* printer;
105   diag::Style style;
106   std::stringstream stream;
107 };
108 
Formatter()109 Formatter::Formatter() {}
Formatter(const Style & style)110 Formatter::Formatter(const Style& style) : style_(style) {}
111 
format(const List & list,Printer * printer) const112 void Formatter::format(const List& list, Printer* printer) const {
113   State state{printer};
114 
115   bool first = true;
116   for (auto diag : list) {
117     state.set_style({});
118     if (!first) {
119       state.newline();
120     }
121     format(diag, state);
122     first = false;
123   }
124 
125   if (style_.print_newline_at_end) {
126     state.newline();
127   }
128 }
129 
format(const Diagnostic & diag,State & state) const130 void Formatter::format(const Diagnostic& diag, State& state) const {
131   auto const& src = diag.source;
132   auto const& rng = src.range;
133   bool has_code = diag.code != nullptr && diag.code[0] != '\0';
134 
135   state.set_style({Color::kDefault, true});
136 
137   struct TextAndColor {
138     std::string text;
139     Color color;
140     bool bold = false;
141   };
142   std::vector<TextAndColor> prefix;
143   prefix.reserve(6);
144 
145   if (style_.print_file && !src.file_path.empty()) {
146     if (rng.begin.line > 0) {
147       prefix.emplace_back(TextAndColor{src.file_path + ":" + to_str(rng.begin),
148                                        Color::kDefault});
149     } else {
150       prefix.emplace_back(TextAndColor{src.file_path, Color::kDefault});
151     }
152   } else if (rng.begin.line > 0) {
153     prefix.emplace_back(TextAndColor{to_str(rng.begin), Color::kDefault});
154   }
155 
156   Color severity_color = Color::kDefault;
157   switch (diag.severity) {
158     case Severity::Note:
159       break;
160     case Severity::Warning:
161       severity_color = Color::kYellow;
162       break;
163     case Severity::Error:
164       severity_color = Color::kRed;
165       break;
166     case Severity::Fatal:
167     case Severity::InternalCompilerError:
168       severity_color = Color::kMagenta;
169       break;
170   }
171   if (style_.print_severity) {
172     prefix.emplace_back(
173         TextAndColor{to_str(diag.severity), severity_color, true});
174   }
175   if (has_code) {
176     prefix.emplace_back(TextAndColor{diag.code, severity_color});
177   }
178 
179   for (size_t i = 0; i < prefix.size(); i++) {
180     if (i > 0) {
181       state << " ";
182     }
183     state.set_style({prefix[i].color, prefix[i].bold});
184     state << prefix[i].text;
185   }
186 
187   state.set_style({Color::kDefault, true});
188   if (!prefix.empty()) {
189     state << ": ";
190   }
191   state << diag.message;
192 
193   if (style_.print_line && src.file_content != nullptr && rng.begin.line > 0) {
194     state.newline();
195     state.set_style({Color::kDefault, false});
196 
197     for (size_t line_num = rng.begin.line;
198          (line_num <= rng.end.line) &&
199          (line_num <= src.file_content->lines.size());
200          line_num++) {
201       auto& line = src.file_content->lines[line_num - 1];
202       auto line_len = line.size();
203 
204       for (auto c : line) {
205         if (c == '\t') {
206           state.repeat(' ', style_.tab_width);
207         } else {
208           state << c;
209         }
210       }
211 
212       state.newline();
213       state.set_style({Color::kCyan, false});
214 
215       // Count the number of glyphs in the line span.
216       // start and end use 1-based indexing .
217       auto num_glyphs = [&](size_t start, size_t end) {
218         size_t count = 0;
219         start = (start > 0) ? (start - 1) : 0;
220         end = (end > 0) ? (end - 1) : 0;
221         for (size_t i = start; (i < end) && (i < line_len); i++) {
222           count += (line[i] == '\t') ? style_.tab_width : 1;
223         }
224         return count;
225       };
226 
227       if (line_num == rng.begin.line && line_num == rng.end.line) {
228         // Single line
229         state.repeat(' ', num_glyphs(1, rng.begin.column));
230         state.repeat('^', std::max<size_t>(
231                               num_glyphs(rng.begin.column, rng.end.column), 1));
232       } else if (line_num == rng.begin.line) {
233         // Start of multi-line
234         state.repeat(' ', num_glyphs(1, rng.begin.column));
235         state.repeat('^', num_glyphs(rng.begin.column, line_len + 1));
236       } else if (line_num == rng.end.line) {
237         // End of multi-line
238         state.repeat('^', num_glyphs(1, rng.end.column));
239       } else {
240         // Middle of multi-line
241         state.repeat('^', num_glyphs(1, line_len + 1));
242       }
243       state.newline();
244     }
245 
246     state.set_style({});
247   }
248 }
249 
format(const List & list) const250 std::string Formatter::format(const List& list) const {
251   StringPrinter printer;
252   format(list, &printer);
253   return printer.str();
254 }
255 
256 Formatter::~Formatter() = default;
257 
258 }  // namespace diag
259 }  // namespace tint
260