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