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
16namespace {
17
18std::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
26void 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 = static_cast<int>(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).
57void 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
86Err::Err() : has_error_(false) {
87}
88
89Err::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
98Err::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
108Err::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
118Err::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
132Err::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
145Err::~Err() {
146}
147
148void Err::PrintToStdout() const {
149  InternalPrintToStdout(false);
150}
151
152void Err::AppendSubErr(const Err& err) {
153  sub_errs_.push_back(err);
154}
155
156void 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 = location_.Describe(true);
165  if (!loc_str.empty()) {
166    if (is_sub_err)
167      loc_str.insert(0, "See ");
168    else
169      loc_str.insert(0, "at ");
170    loc_str.append(": ");
171  }
172  OutputString(loc_str + message_ + "\n");
173
174  // Quoted line.
175  if (input_file) {
176    std::string line = GetNthLine(input_file->contents(),
177                                  location_.line_number());
178    if (!base::ContainsOnlyChars(line, base::kWhitespaceASCII)) {
179      OutputString(line + "\n", DECORATION_DIM);
180      OutputHighlighedPosition(location_, ranges_, line.size());
181    }
182  }
183
184  // Optional help text.
185  if (!help_text_.empty())
186    OutputString(help_text_ + "\n");
187
188  // Sub errors.
189  for (size_t i = 0; i < sub_errs_.size(); i++)
190    sub_errs_[i].InternalPrintToStdout(true);
191}
192