// Copyright 2020 The Tint Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "src/diagnostic/formatter.h" #include #include #include #include "src/diagnostic/diagnostic.h" #include "src/diagnostic/printer.h" namespace tint { namespace diag { namespace { const char* to_str(Severity severity) { switch (severity) { case Severity::Note: return "note"; case Severity::Warning: return "warning"; case Severity::Error: return "error"; case Severity::InternalCompilerError: return "internal compiler error"; case Severity::Fatal: return "fatal"; } return ""; } std::string to_str(const Source::Location& location) { std::stringstream ss; if (location.line > 0) { ss << location.line; if (location.column > 0) { ss << ":" << location.column; } } return ss.str(); } } // namespace /// State holds the internal formatter state for a format() call. struct Formatter::State { /// Constructs a State associated with the given printer. /// @param p the printer to write formatted messages to. explicit State(Printer* p) : printer(p) {} ~State() { flush(); } /// set_style() sets the current style to new_style, flushing any pending /// messages to the printer if the style changed. /// @param new_style the new style to apply for future written messages. void set_style(const diag::Style& new_style) { if (style.color != new_style.color || style.bold != new_style.bold) { flush(); style = new_style; } } /// flush writes any pending messages to the printer, clearing the buffer. void flush() { auto str = stream.str(); if (str.length() > 0) { printer->write(str, style); std::stringstream reset; stream.swap(reset); } } /// operator<< queues msg to be written to the printer. /// @param msg the value or string to write to the printer /// @returns this State so that calls can be chained template State& operator<<(const T& msg) { stream << msg; return *this; } /// newline queues a newline to be written to the printer. void newline() { stream << std::endl; } /// repeat queues the character c to be written to the printer n times. /// @param c the character to print `n` times /// @param n the number of times to print character `c` void repeat(char c, size_t n) { std::fill_n(std::ostream_iterator(stream), n, c); } private: Printer* printer; diag::Style style; std::stringstream stream; }; Formatter::Formatter() {} Formatter::Formatter(const Style& style) : style_(style) {} void Formatter::format(const List& list, Printer* printer) const { State state{printer}; bool please_report_bug = false; bool first = true; for (auto diag : list) { state.set_style({}); if (!first) { state.newline(); } format(diag, state); first = false; if (static_cast(diag.severity) > static_cast(Severity::Error)) { please_report_bug = true; } } if (please_report_bug) { state.set_style({Color::kRed, true}); state << R"( ******************************************************************** * The tint shader compiler has encountered an unexpected error. * * * * Please help us fix this issue by submitting a bug report at * * crbug.com/tint with the source program that triggered the bug. * ******************************************************************** )"; } if (style_.print_newline_at_end) { state.newline(); } } void Formatter::format(const Diagnostic& diag, State& state) const { auto const& src = diag.source; auto const& rng = src.range; bool has_code = diag.code != nullptr && diag.code[0] != '\0'; state.set_style({Color::kDefault, true}); struct TextAndColor { std::string text; Color color; bool bold = false; }; std::vector prefix; prefix.reserve(6); if (style_.print_file && !src.file_path.empty()) { if (rng.begin.line > 0) { prefix.emplace_back(TextAndColor{src.file_path + ":" + to_str(rng.begin), Color::kDefault}); } else { prefix.emplace_back(TextAndColor{src.file_path, Color::kDefault}); } } else if (rng.begin.line > 0) { prefix.emplace_back(TextAndColor{to_str(rng.begin), Color::kDefault}); } Color severity_color = Color::kDefault; switch (diag.severity) { case Severity::Note: break; case Severity::Warning: severity_color = Color::kYellow; break; case Severity::Error: severity_color = Color::kRed; break; case Severity::Fatal: case Severity::InternalCompilerError: severity_color = Color::kMagenta; break; } if (style_.print_severity) { prefix.emplace_back( TextAndColor{to_str(diag.severity), severity_color, true}); } if (has_code) { prefix.emplace_back(TextAndColor{diag.code, severity_color}); } for (size_t i = 0; i < prefix.size(); i++) { if (i > 0) { state << " "; } state.set_style({prefix[i].color, prefix[i].bold}); state << prefix[i].text; } state.set_style({Color::kDefault, true}); if (!prefix.empty()) { state << ": "; } state << diag.message; if (style_.print_line && src.file_content != nullptr && rng.begin.line > 0) { state.newline(); state.set_style({Color::kDefault, false}); for (size_t line_num = rng.begin.line; (line_num <= rng.end.line) && (line_num <= src.file_content->lines.size()); line_num++) { auto& line = src.file_content->lines[line_num - 1]; auto line_len = line.size(); for (auto c : line) { if (c == '\t') { state.repeat(' ', style_.tab_width); } else { state << c; } } state.newline(); state.set_style({Color::kCyan, false}); // Count the number of glyphs in the line span. // start and end use 1-based indexing . auto num_glyphs = [&](size_t start, size_t end) { size_t count = 0; start = (start > 0) ? (start - 1) : 0; end = (end > 0) ? (end - 1) : 0; for (size_t i = start; (i < end) && (i < line_len); i++) { count += (line[i] == '\t') ? style_.tab_width : 1; } return count; }; if (line_num == rng.begin.line && line_num == rng.end.line) { // Single line state.repeat(' ', num_glyphs(1, rng.begin.column)); state.repeat('^', std::max( num_glyphs(rng.begin.column, rng.end.column), 1)); } else if (line_num == rng.begin.line) { // Start of multi-line state.repeat(' ', num_glyphs(1, rng.begin.column)); state.repeat('^', num_glyphs(rng.begin.column, line_len + 1)); } else if (line_num == rng.end.line) { // End of multi-line state.repeat('^', num_glyphs(1, rng.end.column)); } else { // Middle of multi-line state.repeat('^', num_glyphs(1, line_len + 1)); } state.newline(); } state.set_style({}); } } std::string Formatter::format(const List& list) const { StringPrinter printer; format(list, &printer); return printer.str(); } Formatter::~Formatter() = default; } // namespace diag } // namespace tint