// 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 #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(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(line->size())); CHECK(end_char >= 0 && end_char <= static_cast(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(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(*other.info_); } Err::Err(const Location& location, const std::string& msg, const std::string& help) : info_(std::make_unique(location, msg, help)) {} Err::Err(const LocationRange& range, const std::string& msg, const std::string& help) : info_(std::make_unique(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(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(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(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(*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); }