[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 1 | // Copyright 2014 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/c_include_iterator.h" |
| 6 | |
| 7 | #include "base/logging.h" |
avi | cab0f83 | 2015-12-26 07:00:39 | [diff] [blame] | 8 | #include "base/macros.h" |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 9 | #include "base/strings/string_util.h" |
| 10 | #include "tools/gn/input_file.h" |
| 11 | #include "tools/gn/location.h" |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 12 | |
| 13 | namespace { |
| 14 | |
| 15 | enum IncludeType { |
| 16 | INCLUDE_NONE, |
| 17 | INCLUDE_SYSTEM, // #include <...> |
| 18 | INCLUDE_USER // #include "..." |
| 19 | }; |
| 20 | |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 21 | // Returns a new string piece referencing the same buffer as the argument, but |
| 22 | // with leading space trimmed. This only checks for space and tab characters |
| 23 | // since we're dealing with lines in C source files. |
| 24 | base::StringPiece TrimLeadingWhitespace(const base::StringPiece& str) { |
| 25 | size_t new_begin = 0; |
| 26 | while (new_begin < str.size() && |
| 27 | (str[new_begin] == ' ' || str[new_begin] == '\t')) |
| 28 | new_begin++; |
| 29 | return str.substr(new_begin); |
| 30 | } |
| 31 | |
| 32 | // We don't want to count comment lines and preprocessor lines toward our |
| 33 | // "max lines to look at before giving up" since the beginnings of some files |
| 34 | // may have a lot of comments. |
| 35 | // |
| 36 | // We only handle C-style "//" comments since this is the normal commenting |
| 37 | // style used in Chrome, and do so pretty stupidly. We don't want to write a |
| 38 | // full C++ parser here, we're just trying to get a good heuristic for checking |
| 39 | // the file. |
| 40 | // |
| 41 | // We assume the line has leading whitespace trimmed. We also assume that empty |
| 42 | // lines have already been filtered out. |
| 43 | bool ShouldCountTowardNonIncludeLines(const base::StringPiece& line) { |
tfarina | 934d88d | 2015-12-01 17:41:05 | [diff] [blame] | 44 | if (base::StartsWith(line, "//", base::CompareCase::SENSITIVE)) |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 45 | return false; // Don't count comments. |
tfarina | 934d88d | 2015-12-01 17:41:05 | [diff] [blame] | 46 | if (base::StartsWith(line, "/*", base::CompareCase::SENSITIVE) || |
| 47 | base::StartsWith(line, " *", base::CompareCase::SENSITIVE)) |
brettw | a2d721a | 2015-10-16 05:34:24 | [diff] [blame] | 48 | return false; // C-style comment blocks with stars along the left side. |
tfarina | 934d88d | 2015-12-01 17:41:05 | [diff] [blame] | 49 | if (base::StartsWith(line, "#", base::CompareCase::SENSITIVE)) |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 50 | return false; // Don't count preprocessor. |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 51 | if (base::ContainsOnlyChars(line, base::kWhitespaceASCII)) |
| 52 | return false; // Don't count whitespace lines. |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 53 | return true; // Count everything else. |
| 54 | } |
| 55 | |
| 56 | // Given a line, checks to see if it looks like an include or import and |
| 57 | // extract the path. The type of include is returned. Returns INCLUDE_NONE on |
| 58 | // error or if this is not an include line. |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 59 | // |
| 60 | // The 1-based character number on the line that the include was found at |
| 61 | // will be filled into *begin_char. |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 62 | IncludeType ExtractInclude(const base::StringPiece& line, |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 63 | base::StringPiece* path, |
| 64 | int* begin_char) { |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 65 | static const char kInclude[] = "#include"; |
| 66 | static const size_t kIncludeLen = arraysize(kInclude) - 1; // No null. |
| 67 | static const char kImport[] = "#import"; |
| 68 | static const size_t kImportLen = arraysize(kImport) - 1; // No null. |
| 69 | |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 70 | base::StringPiece trimmed = TrimLeadingWhitespace(line); |
| 71 | if (trimmed.empty()) |
| 72 | return INCLUDE_NONE; |
| 73 | |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 74 | base::StringPiece contents; |
tfarina | 934d88d | 2015-12-01 17:41:05 | [diff] [blame] | 75 | if (base::StartsWith(trimmed, base::StringPiece(kInclude, kIncludeLen), |
| 76 | base::CompareCase::SENSITIVE)) |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 77 | contents = TrimLeadingWhitespace(trimmed.substr(kIncludeLen)); |
tfarina | 934d88d | 2015-12-01 17:41:05 | [diff] [blame] | 78 | else if (base::StartsWith(trimmed, base::StringPiece(kImport, kImportLen), |
| 79 | base::CompareCase::SENSITIVE)) |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 80 | contents = TrimLeadingWhitespace(trimmed.substr(kImportLen)); |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 81 | |
| 82 | if (contents.empty()) |
| 83 | return INCLUDE_NONE; |
| 84 | |
| 85 | IncludeType type = INCLUDE_NONE; |
| 86 | char terminating_char = 0; |
| 87 | if (contents[0] == '"') { |
| 88 | type = INCLUDE_USER; |
| 89 | terminating_char = '"'; |
| 90 | } else if (contents[0] == '<') { |
| 91 | type = INCLUDE_SYSTEM; |
| 92 | terminating_char = '>'; |
| 93 | } else { |
| 94 | return INCLUDE_NONE; |
| 95 | } |
| 96 | |
| 97 | // Count everything to next "/> as the contents. |
| 98 | size_t terminator_index = contents.find(terminating_char, 1); |
| 99 | if (terminator_index == base::StringPiece::npos) |
| 100 | return INCLUDE_NONE; |
| 101 | |
| 102 | *path = contents.substr(1, terminator_index - 1); |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 103 | // Note: one based so we do "+ 1". |
| 104 | *begin_char = static_cast<int>(path->data() - line.data()) + 1; |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 105 | return type; |
| 106 | } |
| 107 | |
brettw | 20982dc | 2015-07-06 22:37:06 | [diff] [blame] | 108 | // Returns true if this line has a "nogncheck" comment associated with it. |
| 109 | bool HasNoCheckAnnotation(const base::StringPiece& line) { |
| 110 | return line.find("nogncheck") != base::StringPiece::npos; |
| 111 | } |
| 112 | |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 113 | } // namespace |
| 114 | |
| 115 | const int CIncludeIterator::kMaxNonIncludeLines = 10; |
| 116 | |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 117 | CIncludeIterator::CIncludeIterator(const InputFile* input) |
| 118 | : input_file_(input), |
| 119 | file_(input->contents()), |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 120 | offset_(0), |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 121 | line_number_(0), |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 122 | lines_since_last_include_(0) { |
| 123 | } |
| 124 | |
| 125 | CIncludeIterator::~CIncludeIterator() { |
| 126 | } |
| 127 | |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 128 | bool CIncludeIterator::GetNextIncludeString(base::StringPiece* out, |
| 129 | LocationRange* location) { |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 130 | base::StringPiece line; |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 131 | int cur_line_number = 0; |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 132 | while (lines_since_last_include_ <= kMaxNonIncludeLines && |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 133 | GetNextLine(&line, &cur_line_number)) { |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 134 | base::StringPiece include_contents; |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 135 | int begin_char; |
| 136 | IncludeType type = ExtractInclude(line, &include_contents, &begin_char); |
brettw | 20982dc | 2015-07-06 22:37:06 | [diff] [blame] | 137 | if (type == INCLUDE_USER && !HasNoCheckAnnotation(line)) { |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 138 | // Only count user includes for now. |
| 139 | *out = include_contents; |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 140 | *location = LocationRange( |
scottmg | be5d451 | 2014-09-24 02:29:12 | [diff] [blame] | 141 | Location(input_file_, |
| 142 | cur_line_number, |
| 143 | begin_char, |
| 144 | -1 /* TODO(scottmg): Is this important? */), |
| 145 | Location(input_file_, |
| 146 | cur_line_number, |
| 147 | begin_char + static_cast<int>(include_contents.size()), |
| 148 | -1 /* TODO(scottmg): Is this important? */)); |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 149 | |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 150 | lines_since_last_include_ = 0; |
| 151 | return true; |
| 152 | } |
| 153 | |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 154 | if (ShouldCountTowardNonIncludeLines(line)) |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 155 | lines_since_last_include_++; |
| 156 | } |
| 157 | return false; |
| 158 | } |
| 159 | |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 160 | bool CIncludeIterator::GetNextLine(base::StringPiece* line, int* line_number) { |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 161 | if (offset_ == file_.size()) |
| 162 | return false; |
| 163 | |
| 164 | size_t begin = offset_; |
| 165 | while (offset_ < file_.size() && file_[offset_] != '\n') |
| 166 | offset_++; |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 167 | line_number_++; |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 168 | |
| 169 | *line = file_.substr(begin, offset_ - begin); |
[email protected] | fce5c3fe | 2014-04-10 21:13:05 | [diff] [blame] | 170 | *line_number = line_number_; |
[email protected] | 126d8b5 | 2014-04-07 22:17:35 | [diff] [blame] | 171 | |
| 172 | // If we didn't hit EOF, skip past the newline for the next one. |
| 173 | if (offset_ < file_.size()) |
| 174 | offset_++; |
| 175 | return true; |
| 176 | } |