197 lines
5.5 KiB
C++
197 lines
5.5 KiB
C++
// Copyright (c) 2013 The Chromium 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 "tools/gn/err.h"
|
|
|
|
#include "base/strings/string_number_conversions.h"
|
|
#include "base/strings/string_util.h"
|
|
#include "tools/gn/filesystem_utils.h"
|
|
#include "tools/gn/input_file.h"
|
|
#include "tools/gn/parse_tree.h"
|
|
#include "tools/gn/standard_out.h"
|
|
#include "tools/gn/tokenizer.h"
|
|
#include "tools/gn/value.h"
|
|
|
|
namespace {
|
|
|
|
std::string GetNthLine(const base::StringPiece& data, int n) {
|
|
size_t line_off = Tokenizer::ByteOffsetOfNthLine(data, n);
|
|
size_t end = line_off + 1;
|
|
while (end < data.size() && !Tokenizer::IsNewline(data, end))
|
|
end++;
|
|
return data.substr(line_off, end - line_off).as_string();
|
|
}
|
|
|
|
void FillRangeOnLine(const LocationRange& range, int line_number,
|
|
std::string* line) {
|
|
// Only bother if the range's begin or end overlaps the line. If the entire
|
|
// line is highlighted as a result of this range, it's not very helpful.
|
|
if (range.begin().line_number() != line_number &&
|
|
range.end().line_number() != line_number)
|
|
return;
|
|
|
|
// Watch out, the char offsets in the location are 1-based, so we have to
|
|
// subtract 1.
|
|
int begin_char;
|
|
if (range.begin().line_number() < line_number)
|
|
begin_char = 0;
|
|
else
|
|
begin_char = range.begin().char_offset() - 1;
|
|
|
|
int end_char;
|
|
if (range.end().line_number() > line_number)
|
|
end_char = line->size(); // Ending is non-inclusive.
|
|
else
|
|
end_char = range.end().char_offset() - 1;
|
|
|
|
CHECK(end_char >= begin_char);
|
|
CHECK(begin_char >= 0 && begin_char <= static_cast<int>(line->size()));
|
|
CHECK(end_char >= 0 && end_char <= static_cast<int>(line->size()));
|
|
for (int i = begin_char; i < end_char; i++)
|
|
line->at(i) = '-';
|
|
}
|
|
|
|
// The line length is used to clip the maximum length of the markers we'll
|
|
// make if the error spans more than one line (like unterminated literals).
|
|
void OutputHighlighedPosition(const Location& location,
|
|
const Err::RangeList& ranges,
|
|
size_t line_length) {
|
|
// Make a buffer of the line in spaces.
|
|
std::string highlight;
|
|
highlight.resize(line_length);
|
|
for (size_t i = 0; i < line_length; i++)
|
|
highlight[i] = ' ';
|
|
|
|
// Highlight all the ranges on the line.
|
|
for (size_t i = 0; i < ranges.size(); i++)
|
|
FillRangeOnLine(ranges[i], location.line_number(), &highlight);
|
|
|
|
// Allow the marker to be one past the end of the line for marking the end.
|
|
highlight.push_back(' ');
|
|
CHECK(location.char_offset() - 1 >= 0 &&
|
|
location.char_offset() - 1 < static_cast<int>(highlight.size()));
|
|
highlight[location.char_offset() - 1] = '^';
|
|
|
|
// Trim unused spaces from end of line.
|
|
while (!highlight.empty() && highlight[highlight.size() - 1] == ' ')
|
|
highlight.resize(highlight.size() - 1);
|
|
|
|
highlight += "\n";
|
|
OutputString(highlight, DECORATION_BLUE);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Err::Err() : has_error_(false) {
|
|
}
|
|
|
|
Err::Err(const Location& location,
|
|
const std::string& msg,
|
|
const std::string& help)
|
|
: has_error_(true),
|
|
location_(location),
|
|
message_(msg),
|
|
help_text_(help) {
|
|
}
|
|
|
|
Err::Err(const LocationRange& range,
|
|
const std::string& msg,
|
|
const std::string& help)
|
|
: has_error_(true),
|
|
location_(range.begin()),
|
|
message_(msg),
|
|
help_text_(help) {
|
|
ranges_.push_back(range);
|
|
}
|
|
|
|
Err::Err(const Token& token,
|
|
const std::string& msg,
|
|
const std::string& help)
|
|
: has_error_(true),
|
|
location_(token.location()),
|
|
message_(msg),
|
|
help_text_(help) {
|
|
ranges_.push_back(token.range());
|
|
}
|
|
|
|
Err::Err(const ParseNode* node,
|
|
const std::string& msg,
|
|
const std::string& help_text)
|
|
: has_error_(true),
|
|
message_(msg),
|
|
help_text_(help_text) {
|
|
// Node will be null in certain tests.
|
|
if (node) {
|
|
LocationRange range = node->GetRange();
|
|
location_ = range.begin();
|
|
ranges_.push_back(range);
|
|
}
|
|
}
|
|
|
|
Err::Err(const Value& value,
|
|
const std::string msg,
|
|
const std::string& help_text)
|
|
: has_error_(true),
|
|
message_(msg),
|
|
help_text_(help_text) {
|
|
if (value.origin()) {
|
|
LocationRange range = value.origin()->GetRange();
|
|
location_ = range.begin();
|
|
ranges_.push_back(range);
|
|
}
|
|
}
|
|
|
|
Err::~Err() {
|
|
}
|
|
|
|
void Err::PrintToStdout() const {
|
|
InternalPrintToStdout(false);
|
|
}
|
|
|
|
void Err::AppendSubErr(const Err& err) {
|
|
sub_errs_.push_back(err);
|
|
}
|
|
|
|
void Err::InternalPrintToStdout(bool is_sub_err) const {
|
|
DCHECK(has_error_);
|
|
|
|
if (!is_sub_err)
|
|
OutputString("ERROR ", DECORATION_RED);
|
|
|
|
// File name and location.
|
|
const InputFile* input_file = location_.file();
|
|
std::string loc_str;
|
|
if (input_file) {
|
|
std::string path8;
|
|
path8.assign(input_file->name().value());
|
|
|
|
if (is_sub_err)
|
|
loc_str = "See ";
|
|
else
|
|
loc_str = "at ";
|
|
loc_str += path8 + ": " +
|
|
base::IntToString(location_.line_number()) + ":" +
|
|
base::IntToString(location_.char_offset()) + ": ";
|
|
}
|
|
OutputString(loc_str + message_ + "\n");
|
|
|
|
// Quoted line.
|
|
if (input_file) {
|
|
std::string line = GetNthLine(input_file->contents(),
|
|
location_.line_number());
|
|
if (!ContainsOnlyWhitespaceASCII(line)) {
|
|
OutputString(line + "\n", DECORATION_BOLD);
|
|
OutputHighlighedPosition(location_, ranges_, line.size());
|
|
}
|
|
}
|
|
|
|
// Optional help text.
|
|
if (!help_text_.empty())
|
|
OutputString(help_text_ + "\n");
|
|
|
|
// Sub errors.
|
|
for (size_t i = 0; i < sub_errs_.size(); i++)
|
|
sub_errs_[i].InternalPrintToStdout(true);
|
|
}
|