| // Copyright 2020 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #include "src/tint/utils/diagnostic/formatter.h" | |
| #include <algorithm> | |
| #include <iterator> | |
| #include <utility> | |
| #include <vector> | |
| #include "src/tint/utils/diagnostic/diagnostic.h" | |
| #include "src/tint/utils/diagnostic/printer.h" | |
| #include "src/tint/utils/text/string_stream.h" | |
| namespace tint::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) { | |
| 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); | |
| 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<<(T&& msg) { | |
| stream << std::forward<T>(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) { stream.repeat(c, n); } | |
| private: | |
| Printer* printer; | |
| diag::Style style; | |
| StringStream stream; | |
| }; | |
| Formatter::Formatter() {} | |
| Formatter::Formatter(const Style& style) : style_(style) {} | |
| void Formatter::format(const List& list, Printer* printer) const { | |
| State state{printer}; | |
| bool first = true; | |
| for (auto diag : list) { | |
| state.set_style({}); | |
| if (!first) { | |
| state.newline(); | |
| } | |
| format(diag, state); | |
| first = false; | |
| } | |
| 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; | |
| 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 != nullptr) { | |
| 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}); | |
| } | |
| 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 && 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(); | |
| bool is_ascii = true; | |
| for (auto c : line) { | |
| if (c == '\t') { | |
| state.repeat(' ', style_.tab_width); | |
| } else { | |
| state << c; | |
| } | |
| if (c & 0x80) { | |
| is_ascii = false; | |
| } | |
| } | |
| state.newline(); | |
| // If the line contains non-ascii characters, then we cannot assume that | |
| // a single utf8 code unit represents a single glyph, so don't attempt to | |
| // draw squiggles. | |
| if (!is_ascii) { | |
| continue; | |
| } | |
| 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<size_t>(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 tint::diag |