blob: 5b5c5741975d301dae07c4b22184f8d47eb5132e [file] [log] [blame]
// 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/macros/defer.h"
#include "src/tint/utils/text/string_stream.h"
#include "src/tint/utils/text/styled_text.h"
#include "src/tint/utils/text/styled_text_printer.h"
#include "src/tint/utils/text/text_style.h"
namespace tint::diag {
namespace {
const char* ToString(Severity severity) {
switch (severity) {
case Severity::Note:
return "note";
case Severity::Warning:
return "warning";
case Severity::Error:
return "error";
}
return "";
}
std::string ToString(const Source::Location& location) {
StringStream ss;
if (location.line > 0) {
ss << location.line;
if (location.column > 0) {
ss << ":" << location.column;
}
}
return ss.str();
}
} // namespace
Formatter::Formatter() {}
Formatter::Formatter(const Style& style) : style_(style) {}
StyledText Formatter::Format(const List& list) const {
StyledText text;
bool first = true;
for (auto diag : list) {
if (!first) {
text << "\n";
}
Format(diag, text);
first = false;
}
if (style_.print_newline_at_end) {
text << "\n";
}
return text;
}
void Formatter::Format(const Diagnostic& diag, StyledText& text) const {
auto const& src = diag.source;
auto const& rng = src.range;
text << style::Plain;
TINT_DEFER(text << style::Plain);
struct TextAndStyle {
std::string text;
TextStyle style = {};
};
Vector<TextAndStyle, 6> prefix;
if (style_.print_file && src.file != nullptr) {
if (rng.begin.line > 0) {
prefix.Push(TextAndStyle{src.file->path + ":" + ToString(rng.begin)});
} else {
prefix.Push(TextAndStyle{src.file->path});
}
} else if (rng.begin.line > 0) {
prefix.Push(TextAndStyle{ToString(rng.begin)});
}
if (style_.print_severity) {
TextStyle style;
switch (diag.severity) {
case Severity::Note:
break;
case Severity::Warning:
style = style::Warning + style::Bold;
break;
case Severity::Error:
style = style::Error + style::Bold;
break;
}
prefix.Push(TextAndStyle{ToString(diag.severity), style});
}
for (size_t i = 0; i < prefix.Length(); i++) {
if (i > 0) {
text << " ";
}
text << prefix[i].style << prefix[i].text;
}
if (!prefix.IsEmpty()) {
text << style::Plain(": ");
}
text << style::Bold << diag.message;
if (style_.print_line && src.file && rng.begin.line > 0) {
text << style::Plain("\n");
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') {
text.Repeat(' ', style_.tab_width);
} else {
text << c;
}
if (c & 0x80) {
is_ascii = false;
}
}
text << style::Plain("\n");
// 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;
}
text << style::Squiggle;
// 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
text.Repeat(' ', num_glyphs(1, rng.begin.column));
text.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
text.Repeat(' ', num_glyphs(1, rng.begin.column));
text.Repeat('^', num_glyphs(rng.begin.column, line_len + 1));
} else if (line_num == rng.end.line) {
// End of multi-line
text.Repeat('^', num_glyphs(1, rng.end.column));
} else {
// Middle of multi-line
text.Repeat('^', num_glyphs(1, line_len + 1));
}
text << style::Plain("\n");
}
}
}
Formatter::~Formatter() = default;
} // namespace tint::diag