| // 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 "gn/err.h" |
| |
| #include <stddef.h> |
| |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "gn/filesystem_utils.h" |
| #include "gn/input_file.h" |
| #include "gn/parse_tree.h" |
| #include "gn/standard_out.h" |
| #include "gn/tokenizer.h" |
| #include "gn/value.h" |
| |
| namespace { |
| |
| std::string GetNthLine(std::string_view 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 std::string(data.substr(line_off, end - line_off)); |
| } |
| |
| 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().column_number() - 1; |
| |
| int end_char; |
| if (range.end().line_number() > line_number) |
| end_char = static_cast<int>(line->size()); // Ending is non-inclusive. |
| else |
| end_char = range.end().column_number() - 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 (const auto& range : ranges) |
| FillRangeOnLine(range, 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.column_number() - 1 >= 0 && |
| location.column_number() - 1 < static_cast<int>(highlight.size())); |
| highlight[location.column_number() - 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(const Err& other) { |
| if (other.info_) |
| info_ = std::make_unique<ErrInfo>(*other.info_); |
| } |
| |
| Err::Err(const Location& location, |
| const std::string& msg, |
| const std::string& help) |
| : info_(std::make_unique<ErrInfo>(location, msg, help)) {} |
| |
| Err::Err(const LocationRange& range, |
| const std::string& msg, |
| const std::string& help) |
| : info_(std::make_unique<ErrInfo>(range.begin(), msg, help)) { |
| info_->ranges.push_back(range); |
| } |
| |
| Err::Err(const Token& token, const std::string& msg, const std::string& help) |
| : info_(std::make_unique<ErrInfo>(token.location(), msg, help)) { |
| info_->ranges.push_back(token.range()); |
| } |
| |
| Err::Err(const ParseNode* node, |
| const std::string& msg, |
| const std::string& help_text) |
| : info_(std::make_unique<ErrInfo>(Location(), msg, help_text)) { |
| // Node will be null in certain tests. |
| if (node) { |
| LocationRange range = node->GetRange(); |
| info_->location = range.begin(); |
| info_->ranges.push_back(range); |
| } |
| } |
| |
| Err::Err(const Value& value, |
| const std::string& msg, |
| const std::string& help_text) |
| : info_(std::make_unique<ErrInfo>(Location(), msg, help_text)) { |
| if (value.origin()) { |
| LocationRange range = value.origin()->GetRange(); |
| info_->location = range.begin(); |
| info_->ranges.push_back(range); |
| } |
| } |
| |
| Err& Err::operator=(const Err& other) { |
| if (other.info_) { |
| info_ = std::make_unique<ErrInfo>(*other.info_); |
| } else { |
| info_.reset(); |
| } |
| return *this; |
| } |
| |
| void Err::PrintToStdout() const { |
| InternalPrintToStdout(false, true); |
| } |
| |
| void Err::PrintNonfatalToStdout() const { |
| InternalPrintToStdout(false, false); |
| } |
| |
| void Err::AppendSubErr(const Err& err) { |
| info_->sub_errs.push_back(err); |
| } |
| |
| void Err::InternalPrintToStdout(bool is_sub_err, bool is_fatal) const { |
| DCHECK(info_); |
| |
| if (!is_sub_err) { |
| if (is_fatal) |
| OutputString("ERROR ", DECORATION_RED); |
| else |
| OutputString("WARNING ", DECORATION_RED); |
| } |
| |
| // File name and location. |
| const InputFile* input_file = info_->location.file(); |
| std::string loc_str = info_->location.Describe(true); |
| if (!loc_str.empty()) { |
| if (is_sub_err) |
| loc_str.insert(0, "See "); |
| else |
| loc_str.insert(0, "at "); |
| if (!info_->toolchain_label.is_null()) |
| loc_str += " "; |
| } |
| std::string toolchain_str; |
| if (!info_->toolchain_label.is_null()) { |
| toolchain_str += "(" + info_->toolchain_label.GetUserVisibleName(false) + ")"; |
| } |
| std::string colon; |
| if (!loc_str.empty() || !toolchain_str.empty()) |
| colon = ": "; |
| OutputString(loc_str + toolchain_str + colon + info_->message + "\n"); |
| |
| // Quoted line. |
| if (input_file) { |
| std::string line = |
| GetNthLine(input_file->contents(), info_->location.line_number()); |
| if (!base::ContainsOnlyChars(line, base::kWhitespaceASCII)) { |
| OutputString(line + "\n", DECORATION_DIM); |
| OutputHighlighedPosition(info_->location, info_->ranges, line.size()); |
| } |
| } |
| |
| // Optional help text. |
| if (!info_->help_text.empty()) |
| OutputString(info_->help_text + "\n"); |
| |
| // Sub errors. |
| for (const auto& sub_err : info_->sub_errs) |
| sub_err.InternalPrintToStdout(true, is_fatal); |
| } |