1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "tools/gn/err.h" 6 7 #include "base/strings/string_number_conversions.h" 8 #include "base/strings/string_util.h" 9 #include "tools/gn/filesystem_utils.h" 10 #include "tools/gn/input_file.h" 11 #include "tools/gn/parse_tree.h" 12 #include "tools/gn/standard_out.h" 13 #include "tools/gn/tokenizer.h" 14 #include "tools/gn/value.h" 15 16 namespace { 17 18 std::string GetNthLine(const base::StringPiece& data, int n) { 19 size_t line_off = Tokenizer::ByteOffsetOfNthLine(data, n); 20 size_t end = line_off + 1; 21 while (end < data.size() && !Tokenizer::IsNewline(data, end)) 22 end++; 23 return data.substr(line_off, end - line_off).as_string(); 24 } 25 26 void FillRangeOnLine(const LocationRange& range, int line_number, 27 std::string* line) { 28 // Only bother if the range's begin or end overlaps the line. If the entire 29 // line is highlighted as a result of this range, it's not very helpful. 30 if (range.begin().line_number() != line_number && 31 range.end().line_number() != line_number) 32 return; 33 34 // Watch out, the char offsets in the location are 1-based, so we have to 35 // subtract 1. 36 int begin_char; 37 if (range.begin().line_number() < line_number) 38 begin_char = 0; 39 else 40 begin_char = range.begin().char_offset() - 1; 41 42 int end_char; 43 if (range.end().line_number() > line_number) 44 end_char = line->size(); // Ending is non-inclusive. 45 else 46 end_char = range.end().char_offset() - 1; 47 48 CHECK(end_char >= begin_char); 49 CHECK(begin_char >= 0 && begin_char <= static_cast<int>(line->size())); 50 CHECK(end_char >= 0 && end_char <= static_cast<int>(line->size())); 51 for (int i = begin_char; i < end_char; i++) 52 line->at(i) = '-'; 53 } 54 55 // The line length is used to clip the maximum length of the markers we'll 56 // make if the error spans more than one line (like unterminated literals). 57 void OutputHighlighedPosition(const Location& location, 58 const Err::RangeList& ranges, 59 size_t line_length) { 60 // Make a buffer of the line in spaces. 61 std::string highlight; 62 highlight.resize(line_length); 63 for (size_t i = 0; i < line_length; i++) 64 highlight[i] = ' '; 65 66 // Highlight all the ranges on the line. 67 for (size_t i = 0; i < ranges.size(); i++) 68 FillRangeOnLine(ranges[i], location.line_number(), &highlight); 69 70 // Allow the marker to be one past the end of the line for marking the end. 71 highlight.push_back(' '); 72 CHECK(location.char_offset() - 1 >= 0 && 73 location.char_offset() - 1 < static_cast<int>(highlight.size())); 74 highlight[location.char_offset() - 1] = '^'; 75 76 // Trim unused spaces from end of line. 77 while (!highlight.empty() && highlight[highlight.size() - 1] == ' ') 78 highlight.resize(highlight.size() - 1); 79 80 highlight += "\n"; 81 OutputString(highlight, DECORATION_BLUE); 82 } 83 84 } // namespace 85 86 Err::Err() : has_error_(false) { 87 } 88 89 Err::Err(const Location& location, 90 const std::string& msg, 91 const std::string& help) 92 : has_error_(true), 93 location_(location), 94 message_(msg), 95 help_text_(help) { 96 } 97 98 Err::Err(const LocationRange& range, 99 const std::string& msg, 100 const std::string& help) 101 : has_error_(true), 102 location_(range.begin()), 103 message_(msg), 104 help_text_(help) { 105 ranges_.push_back(range); 106 } 107 108 Err::Err(const Token& token, 109 const std::string& msg, 110 const std::string& help) 111 : has_error_(true), 112 location_(token.location()), 113 message_(msg), 114 help_text_(help) { 115 ranges_.push_back(token.range()); 116 } 117 118 Err::Err(const ParseNode* node, 119 const std::string& msg, 120 const std::string& help_text) 121 : has_error_(true), 122 message_(msg), 123 help_text_(help_text) { 124 // Node will be null in certain tests. 125 if (node) { 126 LocationRange range = node->GetRange(); 127 location_ = range.begin(); 128 ranges_.push_back(range); 129 } 130 } 131 132 Err::Err(const Value& value, 133 const std::string msg, 134 const std::string& help_text) 135 : has_error_(true), 136 message_(msg), 137 help_text_(help_text) { 138 if (value.origin()) { 139 LocationRange range = value.origin()->GetRange(); 140 location_ = range.begin(); 141 ranges_.push_back(range); 142 } 143 } 144 145 Err::~Err() { 146 } 147 148 void Err::PrintToStdout() const { 149 InternalPrintToStdout(false); 150 } 151 152 void Err::AppendSubErr(const Err& err) { 153 sub_errs_.push_back(err); 154 } 155 156 void Err::InternalPrintToStdout(bool is_sub_err) const { 157 DCHECK(has_error_); 158 159 if (!is_sub_err) 160 OutputString("ERROR ", DECORATION_RED); 161 162 // File name and location. 163 const InputFile* input_file = location_.file(); 164 std::string loc_str; 165 if (input_file) { 166 std::string path8; 167 path8.assign(input_file->name().value()); 168 169 if (is_sub_err) 170 loc_str = "See "; 171 else 172 loc_str = "at "; 173 loc_str += path8 + ": " + 174 base::IntToString(location_.line_number()) + ":" + 175 base::IntToString(location_.char_offset()) + ": "; 176 } 177 OutputString(loc_str + message_ + "\n"); 178 179 // Quoted line. 180 if (input_file) { 181 std::string line = GetNthLine(input_file->contents(), 182 location_.line_number()); 183 if (!ContainsOnlyWhitespaceASCII(line)) { 184 OutputString(line + "\n", DECORATION_BOLD); 185 OutputHighlighedPosition(location_, ranges_, line.size()); 186 } 187 } 188 189 // Optional help text. 190 if (!help_text_.empty()) 191 OutputString(help_text_ + "\n"); 192 193 // Sub errors. 194 for (size_t i = 0; i < sub_errs_.size(); i++) 195 sub_errs_[i].InternalPrintToStdout(true); 196 } 197