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(std::string_view data,int n)20 std::string GetNthLine(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(const Err & other)89 Err::Err(const Err& other) {
90 if (other.info_)
91 info_ = std::make_unique<ErrInfo>(*other.info_);
92 }
93
Err(const Location & location,const std::string & msg,const std::string & help)94 Err::Err(const Location& location,
95 const std::string& msg,
96 const std::string& help)
97 : info_(std::make_unique<ErrInfo>(location, msg, help)) {}
98
Err(const LocationRange & range,const std::string & msg,const std::string & help)99 Err::Err(const LocationRange& range,
100 const std::string& msg,
101 const std::string& help)
102 : info_(std::make_unique<ErrInfo>(range.begin(), msg, help)) {
103 info_->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 : info_(std::make_unique<ErrInfo>(token.location(), msg, help)) {
108 info_->ranges.push_back(token.range());
109 }
110
Err(const ParseNode * node,const std::string & msg,const std::string & help_text)111 Err::Err(const ParseNode* node,
112 const std::string& msg,
113 const std::string& help_text)
114 : info_(std::make_unique<ErrInfo>(Location(), msg, help_text)) {
115 // Node will be null in certain tests.
116 if (node) {
117 LocationRange range = node->GetRange();
118 info_->location = range.begin();
119 info_->ranges.push_back(range);
120 }
121 }
122
Err(const Value & value,const std::string & msg,const std::string & help_text)123 Err::Err(const Value& value,
124 const std::string& msg,
125 const std::string& help_text)
126 : info_(std::make_unique<ErrInfo>(Location(), msg, help_text)) {
127 if (value.origin()) {
128 LocationRange range = value.origin()->GetRange();
129 info_->location = range.begin();
130 info_->ranges.push_back(range);
131 }
132 }
133
operator =(const Err & other)134 Err& Err::operator=(const Err& other) {
135 if (other.info_) {
136 info_ = std::make_unique<ErrInfo>(*other.info_);
137 } else {
138 info_.reset();
139 }
140 return *this;
141 }
142
PrintToStdout() const143 void Err::PrintToStdout() const {
144 InternalPrintToStdout(false, true);
145 }
146
PrintNonfatalToStdout() const147 void Err::PrintNonfatalToStdout() const {
148 InternalPrintToStdout(false, false);
149 }
150
AppendSubErr(const Err & err)151 void Err::AppendSubErr(const Err& err) {
152 info_->sub_errs.push_back(err);
153 }
154
InternalPrintToStdout(bool is_sub_err,bool is_fatal) const155 void Err::InternalPrintToStdout(bool is_sub_err, bool is_fatal) const {
156 DCHECK(info_);
157
158 if (!is_sub_err) {
159 if (is_fatal)
160 OutputString("ERROR ", DECORATION_RED);
161 else
162 OutputString("WARNING ", DECORATION_RED);
163 }
164
165 // File name and location.
166 const InputFile* input_file = info_->location.file();
167 std::string loc_str = info_->location.Describe(true);
168 if (!loc_str.empty()) {
169 if (is_sub_err)
170 loc_str.insert(0, "See ");
171 else
172 loc_str.insert(0, "at ");
173 if (!info_->toolchain_label.is_null())
174 loc_str += " ";
175 }
176 std::string toolchain_str;
177 if (!info_->toolchain_label.is_null()) {
178 toolchain_str += "(" + info_->toolchain_label.GetUserVisibleName(false) + ")";
179 }
180 std::string colon;
181 if (!loc_str.empty() || !toolchain_str.empty())
182 colon = ": ";
183 OutputString(loc_str + toolchain_str + colon + info_->message + "\n");
184
185 // Quoted line.
186 if (input_file) {
187 std::string line =
188 GetNthLine(input_file->contents(), info_->location.line_number());
189 if (!base::ContainsOnlyChars(line, base::kWhitespaceASCII)) {
190 OutputString(line + "\n", DECORATION_DIM);
191 OutputHighlighedPosition(info_->location, info_->ranges, line.size());
192 }
193 }
194
195 // Optional help text.
196 if (!info_->help_text.empty())
197 OutputString(info_->help_text + "\n");
198
199 // Sub errors.
200 for (const auto& sub_err : info_->sub_errs)
201 sub_err.InternalPrintToStdout(true, is_fatal);
202 }
203