11d77d6caf647424f9c1c481145be0465e96c9e3eBrian// Copyright 2014 The Chromium Authors. All rights reserved.
21d77d6caf647424f9c1c481145be0465e96c9e3eBrian// Use of this source code is governed by a BSD-style license that can be
31d77d6caf647424f9c1c481145be0465e96c9e3eBrian// found in the LICENSE file.
41d77d6caf647424f9c1c481145be0465e96c9e3eBrian
51d77d6caf647424f9c1c481145be0465e96c9e3eBrian#include "tools/gn/c_include_iterator.h"
61d77d6caf647424f9c1c481145be0465e96c9e3eBrian
71d77d6caf647424f9c1c481145be0465e96c9e3eBrian#include "base/logging.h"
81d77d6caf647424f9c1c481145be0465e96c9e3eBrian#include "base/strings/string_util.h"
91d77d6caf647424f9c1c481145be0465e96c9e3eBrian#include "tools/gn/input_file.h"
101d77d6caf647424f9c1c481145be0465e96c9e3eBrian#include "tools/gn/location.h"
111d77d6caf647424f9c1c481145be0465e96c9e3eBrian
121d77d6caf647424f9c1c481145be0465e96c9e3eBriannamespace {
131d77d6caf647424f9c1c481145be0465e96c9e3eBrian
141d77d6caf647424f9c1c481145be0465e96c9e3eBrianenum IncludeType {
151d77d6caf647424f9c1c481145be0465e96c9e3eBrian  INCLUDE_NONE,
161d77d6caf647424f9c1c481145be0465e96c9e3eBrian  INCLUDE_SYSTEM,  // #include <...>
171d77d6caf647424f9c1c481145be0465e96c9e3eBrian  INCLUDE_USER     // #include "..."
181d77d6caf647424f9c1c481145be0465e96c9e3eBrian};
191d77d6caf647424f9c1c481145be0465e96c9e3eBrian
201d77d6caf647424f9c1c481145be0465e96c9e3eBrian// Returns true if str starts with the prefix.
211d77d6caf647424f9c1c481145be0465e96c9e3eBrianbool StartsWith(const base::StringPiece& str, const base::StringPiece& prefix) {
221d77d6caf647424f9c1c481145be0465e96c9e3eBrian  base::StringPiece extracted = str.substr(0, prefix.size());
231d77d6caf647424f9c1c481145be0465e96c9e3eBrian  return extracted == prefix;
241d77d6caf647424f9c1c481145be0465e96c9e3eBrian}
251d77d6caf647424f9c1c481145be0465e96c9e3eBrian
261d77d6caf647424f9c1c481145be0465e96c9e3eBrian// Returns a new string piece referencing the same buffer as the argument, but
271d77d6caf647424f9c1c481145be0465e96c9e3eBrian// with leading space trimmed. This only checks for space and tab characters
281d77d6caf647424f9c1c481145be0465e96c9e3eBrian// since we're dealing with lines in C source files.
291d77d6caf647424f9c1c481145be0465e96c9e3eBrianbase::StringPiece TrimLeadingWhitespace(const base::StringPiece& str) {
301d77d6caf647424f9c1c481145be0465e96c9e3eBrian  size_t new_begin = 0;
311d77d6caf647424f9c1c481145be0465e96c9e3eBrian  while (new_begin < str.size() &&
32679b6cf0a0e662513c8d7732049c44916e0e9e86Brian         (str[new_begin] == ' ' || str[new_begin] == '\t'))
331410b7bb509ef37c41043b173bc1047257483af0Brian    new_begin++;
341d77d6caf647424f9c1c481145be0465e96c9e3eBrian  return str.substr(new_begin);
351d77d6caf647424f9c1c481145be0465e96c9e3eBrian}
361d77d6caf647424f9c1c481145be0465e96c9e3eBrian
371d77d6caf647424f9c1c481145be0465e96c9e3eBrian// We don't want to count comment lines and preprocessor lines toward our
381d77d6caf647424f9c1c481145be0465e96c9e3eBrian// "max lines to look at before giving up" since the beginnings of some files
391d77d6caf647424f9c1c481145be0465e96c9e3eBrian// may have a lot of comments.
401d77d6caf647424f9c1c481145be0465e96c9e3eBrian//
411410b7bb509ef37c41043b173bc1047257483af0Brian// We only handle C-style "//" comments since this is the normal commenting
421410b7bb509ef37c41043b173bc1047257483af0Brian// style used in Chrome, and do so pretty stupidly. We don't want to write a
431410b7bb509ef37c41043b173bc1047257483af0Brian// full C++ parser here, we're just trying to get a good heuristic for checking
441410b7bb509ef37c41043b173bc1047257483af0Brian// the file.
451410b7bb509ef37c41043b173bc1047257483af0Brian//
461410b7bb509ef37c41043b173bc1047257483af0Brian// We assume the line has leading whitespace trimmed. We also assume that empty
475f0b49e7a956291842c7ad3a597570cf0db50cb6Michal Krol// lines have already been filtered out.
4807a30e3d18a528a2dc8a247af5c43e7428be1743Dave Airliebool ShouldCountTowardNonIncludeLines(const base::StringPiece& line) {
49abe4f3d1aa68aec70d329447abc890b3eaaba9cbJosé Fonseca  if (StartsWith(line, "//"))
50607a830fe281bb042740ef5cd9ae99df73e19090Michal Krol    return false;  // Don't count comments.
511410b7bb509ef37c41043b173bc1047257483af0Brian  if (StartsWith(line, "#"))
521410b7bb509ef37c41043b173bc1047257483af0Brian    return false;  // Don't count preprocessor.
531410b7bb509ef37c41043b173bc1047257483af0Brian  if (base::ContainsOnlyChars(line, base::kWhitespaceASCII))
54b550d8d76b42ef5ba5e8293dcc24220d5b683369Brian Paul    return false;  // Don't count whitespace lines.
55b550d8d76b42ef5ba5e8293dcc24220d5b683369Brian Paul  return true;  // Count everything else.
56b550d8d76b42ef5ba5e8293dcc24220d5b683369Brian Paul}
571d77d6caf647424f9c1c481145be0465e96c9e3eBrian
581d77d6caf647424f9c1c481145be0465e96c9e3eBrian// Given a line, checks to see if it looks like an include or import and
593197ad5a56ee94773f974ac727b316c5adfe1b6fBrian// extract the path. The type of include is returned. Returns INCLUDE_NONE on
601d77d6caf647424f9c1c481145be0465e96c9e3eBrian// error or if this is not an include line.
611d77d6caf647424f9c1c481145be0465e96c9e3eBrian//
626080e567f0ca1fdcce21e76271d4239c33a50db3Brian Paul// The 1-based character number on the line that the include was found at
631d77d6caf647424f9c1c481145be0465e96c9e3eBrian// will be filled into *begin_char.
641d77d6caf647424f9c1c481145be0465e96c9e3eBrianIncludeType ExtractInclude(const base::StringPiece& line,
651d77d6caf647424f9c1c481145be0465e96c9e3eBrian                           base::StringPiece* path,
66c8c2fc9a7a029bb61520973e55fb3cec18f13e20Jakob Bornecrantz                           int* begin_char) {
67c8c2fc9a7a029bb61520973e55fb3cec18f13e20Jakob Bornecrantz  static const char kInclude[] = "#include";
681d77d6caf647424f9c1c481145be0465e96c9e3eBrian  static const size_t kIncludeLen = arraysize(kInclude) - 1;  // No null.
694ecb2c105da590abf79421a06234b636cd1afcd6Dave Airlie  static const char kImport[] = "#import";
70ff5b0c72db20be099f9fc7dee22aeebbda75ab42Roland Scheidegger  static const size_t kImportLen = arraysize(kImport) - 1;  // No null.
71fb40c5a9c7dc91c03f80780e0a09be0cade98705Brian
72b550d8d76b42ef5ba5e8293dcc24220d5b683369Brian Paul  base::StringPiece trimmed = TrimLeadingWhitespace(line);
73e7ccd703a28e14431b90f29540cec0bf67be1e0fChristoph Bumiller  if (trimmed.empty())
742253906da3c506bb5378a8f2fa203ed0c9021171Brian Paul    return INCLUDE_NONE;
752253906da3c506bb5378a8f2fa203ed0c9021171Brian Paul
762253906da3c506bb5378a8f2fa203ed0c9021171Brian Paul  base::StringPiece contents;
771d77d6caf647424f9c1c481145be0465e96c9e3eBrian  if (StartsWith(trimmed, base::StringPiece(kInclude, kIncludeLen)))
78f7e3e46f72fffe4b83cd3f922173ff28e9ab9c7cDave Airlie    contents = TrimLeadingWhitespace(trimmed.substr(kIncludeLen));
7985206e56a1c3400be47229d4a8c6a1cd7a2f476eBrian Paul  else if (StartsWith(trimmed, base::StringPiece(kImport, kImportLen)))
8085206e56a1c3400be47229d4a8c6a1cd7a2f476eBrian Paul    contents = TrimLeadingWhitespace(trimmed.substr(kImportLen));
8185206e56a1c3400be47229d4a8c6a1cd7a2f476eBrian Paul
8285206e56a1c3400be47229d4a8c6a1cd7a2f476eBrian Paul  if (contents.empty())
8385206e56a1c3400be47229d4a8c6a1cd7a2f476eBrian Paul    return INCLUDE_NONE;
8485206e56a1c3400be47229d4a8c6a1cd7a2f476eBrian Paul
853ff688ea299581e60caf5d6e1a464f68c717fe83Zack Rusin  IncludeType type = INCLUDE_NONE;
863ff688ea299581e60caf5d6e1a464f68c717fe83Zack Rusin  char terminating_char = 0;
873ff688ea299581e60caf5d6e1a464f68c717fe83Zack Rusin  if (contents[0] == '"') {
883ff688ea299581e60caf5d6e1a464f68c717fe83Zack Rusin    type = INCLUDE_USER;
893ff688ea299581e60caf5d6e1a464f68c717fe83Zack Rusin    terminating_char = '"';
903ff688ea299581e60caf5d6e1a464f68c717fe83Zack Rusin  } else if (contents[0] == '<') {
911d77d6caf647424f9c1c481145be0465e96c9e3eBrian    type = INCLUDE_SYSTEM;
921d77d6caf647424f9c1c481145be0465e96c9e3eBrian    terminating_char = '>';
931d77d6caf647424f9c1c481145be0465e96c9e3eBrian  } else {
941d77d6caf647424f9c1c481145be0465e96c9e3eBrian    return INCLUDE_NONE;
951d77d6caf647424f9c1c481145be0465e96c9e3eBrian  }
961d77d6caf647424f9c1c481145be0465e96c9e3eBrian
978223add3304451d5e75737a6d1be1739e4517943Brian Paul  // Count everything to next "/> as the contents.
988223add3304451d5e75737a6d1be1739e4517943Brian Paul  size_t terminator_index = contents.find(terminating_char, 1);
998223add3304451d5e75737a6d1be1739e4517943Brian Paul  if (terminator_index == base::StringPiece::npos)
1008223add3304451d5e75737a6d1be1739e4517943Brian Paul    return INCLUDE_NONE;
1011d77d6caf647424f9c1c481145be0465e96c9e3eBrian
102  *path = contents.substr(1, terminator_index - 1);
103  // Note: one based so we do "+ 1".
104  *begin_char = static_cast<int>(path->data() - line.data()) + 1;
105  return type;
106}
107
108}  // namespace
109
110const int CIncludeIterator::kMaxNonIncludeLines = 10;
111
112CIncludeIterator::CIncludeIterator(const InputFile* input)
113    : input_file_(input),
114      file_(input->contents()),
115      offset_(0),
116      line_number_(0),
117      lines_since_last_include_(0) {
118}
119
120CIncludeIterator::~CIncludeIterator() {
121}
122
123bool CIncludeIterator::GetNextIncludeString(base::StringPiece* out,
124                                            LocationRange* location) {
125  base::StringPiece line;
126  int cur_line_number = 0;
127  while (lines_since_last_include_ <= kMaxNonIncludeLines &&
128         GetNextLine(&line, &cur_line_number)) {
129    base::StringPiece include_contents;
130    int begin_char;
131    IncludeType type = ExtractInclude(line, &include_contents, &begin_char);
132    if (type == INCLUDE_USER) {
133      // Only count user includes for now.
134      *out = include_contents;
135      *location = LocationRange(
136          Location(input_file_,
137                   cur_line_number,
138                   begin_char,
139                   -1 /* TODO(scottmg): Is this important? */),
140          Location(input_file_,
141                   cur_line_number,
142                   begin_char + static_cast<int>(include_contents.size()),
143                   -1 /* TODO(scottmg): Is this important? */));
144
145      lines_since_last_include_ = 0;
146      return true;
147    }
148
149    if (ShouldCountTowardNonIncludeLines(line))
150      lines_since_last_include_++;
151  }
152  return false;
153}
154
155bool CIncludeIterator::GetNextLine(base::StringPiece* line, int* line_number) {
156  if (offset_ == file_.size())
157    return false;
158
159  size_t begin = offset_;
160  while (offset_ < file_.size() && file_[offset_] != '\n')
161    offset_++;
162  line_number_++;
163
164  *line = file_.substr(begin, offset_ - begin);
165  *line_number = line_number_;
166
167  // If we didn't hit EOF, skip past the newline for the next one.
168  if (offset_ < file_.size())
169    offset_++;
170  return true;
171}
172