| // 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 <algorithm> | |
| #include <vector> | |
| #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 <typename T> | |
| 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) { | |
| while (n-- > 0) { | |
| stream << 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<int>(diag.severity) > static_cast<int>(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<TextAndColor> 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 = rng.begin.line; line <= rng.end.line; line++) { | |
| if (line < src.file_content->lines.size() + 1) { | |
| auto len = src.file_content->lines[line - 1].size(); | |
| state << src.file_content->lines[line - 1]; | |
| state.newline(); | |
| state.set_style({Color::kCyan, false}); | |
| if (line == rng.begin.line && line == rng.end.line) { | |
| // Single line | |
| state.repeat(' ', rng.begin.column - 1); | |
| state.repeat('^', | |
| std::max<size_t>(rng.end.column - rng.begin.column, 1)); | |
| } else if (line == rng.begin.line) { | |
| // Start of multi-line | |
| state.repeat(' ', rng.begin.column - 1); | |
| state.repeat('^', len - (rng.begin.column - 1)); | |
| } else if (line == rng.end.line) { | |
| // End of multi-line | |
| state.repeat('^', rng.end.column - 1); | |
| } else { | |
| // Middle of multi-line | |
| state.repeat('^', len); | |
| } | |
| 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 |