| // 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 |