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