blob: 328b96b037df11ffc897df04808f23a1e436cdba [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "fidl/reporter.h"
#include <fidl/diagnostics_json.h>
#include <fidl/token.h>
#include <zircon/assert.h>
#include <sstream>
namespace fidl {
static std::string MakeSquiggle(std::string_view surrounding_line, int column) {
std::string squiggle;
size_t line_size = surrounding_line.size();
for (size_t i = 0; i < (static_cast<size_t>(column) - 1); i++) {
if (i < line_size && surrounding_line[i] == '\t') {
squiggle.push_back('\t');
} else {
squiggle.push_back(' ');
}
}
squiggle.push_back('^');
return squiggle;
}
// static
std::string Reporter::Format(std::string_view qualifier, SourceSpan span, std::string_view message,
bool color) {
ZX_ASSERT_MSG(span.valid(), "diagnostic span must be valid");
const std::string_view bold = color ? "\033[1m" : "";
const std::string_view bold_red = color ? "\033[1;31m" : "";
const std::string_view bold_green = color ? "\033[1;32m" : "";
const std::string_view reset = color ? "\033[0m" : "";
SourceFile::Position position;
std::string surrounding_line = std::string(span.SourceLine(&position));
ZX_ASSERT_MSG(surrounding_line.find('\n') == std::string::npos,
"a single line should not contain a newline character");
std::string squiggle = MakeSquiggle(surrounding_line, position.column);
// If the span is size 0 (ie, something is completely missing), highlight the entire surrounding
// line.
const auto squiggle_size = (span.data().empty() ? surrounding_line.size() : span.data().size());
squiggle += std::string(squiggle_size - 1, '~');
// Some tokens (like string literals) can span multiple lines. Truncate the
// string to just one line at most.
//
// The +1 allows for squiggles at the end of line, which is useful when
// referencing the bounds of a file or line (e.g. unexpected end of file,
// expected something on an empty line).
size_t line_size = surrounding_line.size() + 1;
if (squiggle.size() > line_size) {
squiggle.resize(line_size);
}
std::stringstream error;
// Many editors and IDEs recognize errors in the form of
// filename:linenumber:column: error: descriptive-test-here\n
error << bold << span.position_str() << ": " << reset;
error << bold_red << qualifier << ": " << reset;
error << bold << message << reset;
error << '\n' << surrounding_line << '\n';
error << bold_green << squiggle << reset;
return error.str();
}
void Reporter::AddError(std::unique_ptr<Diagnostic> error) { errors_.push_back(std::move(error)); }
void Reporter::AddWarning(std::unique_ptr<Diagnostic> warning) {
if (warnings_as_errors_) {
errors_.push_back(std::move(warning));
} else {
warnings_.push_back(std::move(warning));
}
}
// Record a diagnostic with the span, message, source line, position indicator,
// and, if span is not nullopt, tildes under the token reported.
//
// filename:line:col: {error, warning}: message
// sourceline
// ^~~~
void Reporter::Report(std::unique_ptr<Diagnostic> diag) {
ZX_ASSERT_MSG(diag, "should not report nullptr diagnostic");
switch (diag->kind) {
case DiagnosticKind::kError:
AddError(std::move(diag));
break;
case DiagnosticKind::kWarning:
AddWarning(std::move(diag));
break;
}
}
std::vector<Diagnostic*> Reporter::Diagnostics() const {
std::vector<Diagnostic*> diagnostics;
diagnostics.reserve(errors_.size() + warnings_.size());
for (const auto& err : errors_) {
diagnostics.push_back(err.get());
}
for (const auto& warn : warnings_) {
diagnostics.push_back(warn.get());
}
// Sort by file > position > kind (errors then warnings) > message.
sort(diagnostics.begin(), diagnostics.end(), [](Diagnostic* a, Diagnostic* b) -> bool {
// SourceSpan overloads the < operator to compare by filename, then
// start position, then end position.
if (a->span < b->span)
return true;
if (b->span < a->span)
return false;
// If neither diagnostic had a span, or if their spans were ==, sort
// by kind (errors first) and then message.
if (a->kind == DiagnosticKind::kError && b->kind == DiagnosticKind::kWarning)
return true;
if (a->kind == DiagnosticKind::kWarning && b->kind == DiagnosticKind::kError)
return false;
return a->msg < b->msg;
});
return diagnostics;
}
void Reporter::PrintReports(bool enable_color) const {
const auto diags = Diagnostics();
for (const auto& diag : diags) {
std::string qualifier = diag->kind == DiagnosticKind::kError ? "error" : "warning";
auto msg = Format(qualifier, diag->span, diag->msg, enable_color);
fprintf(stderr, "%s\n", msg.c_str());
}
if (!errors_.empty() && warnings_.empty()) {
fprintf(stderr, "%zu error(s) reported.\n", errors_.size());
} else if (errors_.empty() && !warnings_.empty()) {
fprintf(stderr, "%zu warning(s) reported.\n", warnings_.size());
} else if (!errors_.empty() && !warnings_.empty()) {
fprintf(stderr, "%zu error(s) and %zu warning(s) reported.\n", errors_.size(),
warnings_.size());
}
}
void Reporter::PrintReportsJson() const {
fprintf(stderr, "%s", fidl::DiagnosticsJson(Diagnostics()).Produce().str().c_str());
}
} // namespace fidl