| // 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/tint/diagnostic/formatter.h" |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <vector> |
| |
| #include "src/tint/diagnostic/diagnostic.h" |
| #include "src/tint/diagnostic/printer.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) { |
| 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) { |
| std::fill_n(std::ostream_iterator<char>(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 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; |
| 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 != 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}); |
| } |
| 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 && 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 |