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" 8 #include "base/strings/string_util.h" 9 #include "tools/gn/input_file.h" 10 #include "tools/gn/location.h" 11 12 namespace { 13 14 enum IncludeType { 15 INCLUDE_NONE, 16 INCLUDE_SYSTEM, // #include <...> 17 INCLUDE_USER // #include "..." 18 }; 19 20 // Returns true if str starts with the prefix. 21 bool StartsWith(const base::StringPiece& str, const base::StringPiece& prefix) { 22 base::StringPiece extracted = str.substr(0, prefix.size()); 23 return extracted == prefix; 24 } 25 26 // Returns a new string piece referencing the same buffer as the argument, but 27 // with leading space trimmed. This only checks for space and tab characters 28 // since we're dealing with lines in C source files. 29 base::StringPiece TrimLeadingWhitespace(const base::StringPiece& str) { 30 size_t new_begin = 0; 31 while (new_begin < str.size() && 32 (str[new_begin] == ' ' || str[new_begin] == '\t')) 33 new_begin++; 34 return str.substr(new_begin); 35 } 36 37 // We don't want to count comment lines and preprocessor lines toward our 38 // "max lines to look at before giving up" since the beginnings of some files 39 // may have a lot of comments. 40 // 41 // We only handle C-style "//" comments since this is the normal commenting 42 // style used in Chrome, and do so pretty stupidly. We don't want to write a 43 // full C++ parser here, we're just trying to get a good heuristic for checking 44 // the file. 45 // 46 // We assume the line has leading whitespace trimmed. We also assume that empty 47 // lines have already been filtered out. 48 bool ShouldCountTowardNonIncludeLines(const base::StringPiece& line) { 49 if (StartsWith(line, "//")) 50 return false; // Don't count comments. 51 if (StartsWith(line, "#")) 52 return false; // Don't count preprocessor. 53 if (base::ContainsOnlyChars(line, base::kWhitespaceASCII)) 54 return false; // Don't count whitespace lines. 55 return true; // Count everything else. 56 } 57 58 // Given a line, checks to see if it looks like an include or import and 59 // extract the path. The type of include is returned. Returns INCLUDE_NONE on 60 // error or if this is not an include line. 61 // 62 // The 1-based character number on the line that the include was found at 63 // will be filled into *begin_char. 64 IncludeType ExtractInclude(const base::StringPiece& line, 65 base::StringPiece* path, 66 int* begin_char) { 67 static const char kInclude[] = "#include"; 68 static const size_t kIncludeLen = arraysize(kInclude) - 1; // No null. 69 static const char kImport[] = "#import"; 70 static const size_t kImportLen = arraysize(kImport) - 1; // No null. 71 72 base::StringPiece trimmed = TrimLeadingWhitespace(line); 73 if (trimmed.empty()) 74 return INCLUDE_NONE; 75 76 base::StringPiece contents; 77 if (StartsWith(trimmed, base::StringPiece(kInclude, kIncludeLen))) 78 contents = TrimLeadingWhitespace(trimmed.substr(kIncludeLen)); 79 else if (StartsWith(trimmed, base::StringPiece(kImport, kImportLen))) 80 contents = TrimLeadingWhitespace(trimmed.substr(kImportLen)); 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); 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 110 const int CIncludeIterator::kMaxNonIncludeLines = 10; 111 112 CIncludeIterator::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 120 CIncludeIterator::~CIncludeIterator() { 121 } 122 123 bool 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 155 bool 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