Index: tools/gn/err.cc |
diff --git a/tools/gn/err.cc b/tools/gn/err.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..068ea6ddce4496a7c06699906d4feb8e3f3a747d |
--- /dev/null |
+++ b/tools/gn/err.cc |
@@ -0,0 +1,196 @@ |
+// 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); |
+} |