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