| // Copyright 2014 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "tools/gn/c_include_iterator.h" | 
 |  | 
 | #include "base/logging.h" | 
 | #include "base/macros.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "tools/gn/input_file.h" | 
 | #include "tools/gn/location.h" | 
 |  | 
 | namespace { | 
 |  | 
 | enum IncludeType { | 
 |   INCLUDE_NONE, | 
 |   INCLUDE_SYSTEM,  // #include <...> | 
 |   INCLUDE_USER     // #include "..." | 
 | }; | 
 |  | 
 | // Returns a new string piece referencing the same buffer as the argument, but | 
 | // with leading space trimmed. This only checks for space and tab characters | 
 | // since we're dealing with lines in C source files. | 
 | base::StringPiece TrimLeadingWhitespace(const base::StringPiece& str) { | 
 |   size_t new_begin = 0; | 
 |   while (new_begin < str.size() && | 
 |          (str[new_begin] == ' ' || str[new_begin] == '\t')) | 
 |     new_begin++; | 
 |   return str.substr(new_begin); | 
 | } | 
 |  | 
 | // We don't want to count comment lines and preprocessor lines toward our | 
 | // "max lines to look at before giving up" since the beginnings of some files | 
 | // may have a lot of comments. | 
 | // | 
 | // We only handle C-style "//" comments since this is the normal commenting | 
 | // style used in Chrome, and do so pretty stupidly. We don't want to write a | 
 | // full C++ parser here, we're just trying to get a good heuristic for checking | 
 | // the file. | 
 | // | 
 | // We assume the line has leading whitespace trimmed. We also assume that empty | 
 | // lines have already been filtered out. | 
 | bool ShouldCountTowardNonIncludeLines(const base::StringPiece& line) { | 
 |   if (base::StartsWith(line, "//", base::CompareCase::SENSITIVE)) | 
 |     return false;  // Don't count comments. | 
 |   if (base::StartsWith(line, "/*", base::CompareCase::SENSITIVE) || | 
 |       base::StartsWith(line, " *", base::CompareCase::SENSITIVE)) | 
 |     return false;  // C-style comment blocks with stars along the left side. | 
 |   if (base::StartsWith(line, "#", base::CompareCase::SENSITIVE)) | 
 |     return false;  // Don't count preprocessor. | 
 |   if (base::ContainsOnlyChars(line, base::kWhitespaceASCII)) | 
 |     return false;  // Don't count whitespace lines. | 
 |   return true;  // Count everything else. | 
 | } | 
 |  | 
 | // Given a line, checks to see if it looks like an include or import and | 
 | // extract the path. The type of include is returned. Returns INCLUDE_NONE on | 
 | // error or if this is not an include line. | 
 | // | 
 | // The 1-based character number on the line that the include was found at | 
 | // will be filled into *begin_char. | 
 | IncludeType ExtractInclude(const base::StringPiece& line, | 
 |                            base::StringPiece* path, | 
 |                            int* begin_char) { | 
 |   static const char kInclude[] = "#include"; | 
 |   static const size_t kIncludeLen = arraysize(kInclude) - 1;  // No null. | 
 |   static const char kImport[] = "#import"; | 
 |   static const size_t kImportLen = arraysize(kImport) - 1;  // No null. | 
 |  | 
 |   base::StringPiece trimmed = TrimLeadingWhitespace(line); | 
 |   if (trimmed.empty()) | 
 |     return INCLUDE_NONE; | 
 |  | 
 |   base::StringPiece contents; | 
 |   if (base::StartsWith(trimmed, base::StringPiece(kInclude, kIncludeLen), | 
 |                        base::CompareCase::SENSITIVE)) | 
 |     contents = TrimLeadingWhitespace(trimmed.substr(kIncludeLen)); | 
 |   else if (base::StartsWith(trimmed, base::StringPiece(kImport, kImportLen), | 
 |                             base::CompareCase::SENSITIVE)) | 
 |     contents = TrimLeadingWhitespace(trimmed.substr(kImportLen)); | 
 |  | 
 |   if (contents.empty()) | 
 |     return INCLUDE_NONE; | 
 |  | 
 |   IncludeType type = INCLUDE_NONE; | 
 |   char terminating_char = 0; | 
 |   if (contents[0] == '"') { | 
 |     type = INCLUDE_USER; | 
 |     terminating_char = '"'; | 
 |   } else if (contents[0] == '<') { | 
 |     type = INCLUDE_SYSTEM; | 
 |     terminating_char = '>'; | 
 |   } else { | 
 |     return INCLUDE_NONE; | 
 |   } | 
 |  | 
 |   // Count everything to next "/> as the contents. | 
 |   size_t terminator_index = contents.find(terminating_char, 1); | 
 |   if (terminator_index == base::StringPiece::npos) | 
 |     return INCLUDE_NONE; | 
 |  | 
 |   *path = contents.substr(1, terminator_index - 1); | 
 |   // Note: one based so we do "+ 1". | 
 |   *begin_char = static_cast<int>(path->data() - line.data()) + 1; | 
 |   return type; | 
 | } | 
 |  | 
 | // Returns true if this line has a "nogncheck" comment associated with it. | 
 | bool HasNoCheckAnnotation(const base::StringPiece& line) { | 
 |   return line.find("nogncheck") != base::StringPiece::npos; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | const int CIncludeIterator::kMaxNonIncludeLines = 10; | 
 |  | 
 | CIncludeIterator::CIncludeIterator(const InputFile* input) | 
 |     : input_file_(input), | 
 |       file_(input->contents()), | 
 |       offset_(0), | 
 |       line_number_(0), | 
 |       lines_since_last_include_(0) { | 
 | } | 
 |  | 
 | CIncludeIterator::~CIncludeIterator() = default; | 
 |  | 
 | bool CIncludeIterator::GetNextIncludeString(base::StringPiece* out, | 
 |                                             LocationRange* location) { | 
 |   base::StringPiece line; | 
 |   int cur_line_number = 0; | 
 |   while (lines_since_last_include_ <= kMaxNonIncludeLines && | 
 |          GetNextLine(&line, &cur_line_number)) { | 
 |     base::StringPiece include_contents; | 
 |     int begin_char; | 
 |     IncludeType type = ExtractInclude(line, &include_contents, &begin_char); | 
 |     if (type == INCLUDE_USER && !HasNoCheckAnnotation(line)) { | 
 |       // Only count user includes for now. | 
 |       *out = include_contents; | 
 |       *location = LocationRange( | 
 |           Location(input_file_, | 
 |                    cur_line_number, | 
 |                    begin_char, | 
 |                    -1 /* TODO(scottmg): Is this important? */), | 
 |           Location(input_file_, | 
 |                    cur_line_number, | 
 |                    begin_char + static_cast<int>(include_contents.size()), | 
 |                    -1 /* TODO(scottmg): Is this important? */)); | 
 |  | 
 |       lines_since_last_include_ = 0; | 
 |       return true; | 
 |     } | 
 |  | 
 |     if (ShouldCountTowardNonIncludeLines(line)) | 
 |       lines_since_last_include_++; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | bool CIncludeIterator::GetNextLine(base::StringPiece* line, int* line_number) { | 
 |   if (offset_ == file_.size()) | 
 |     return false; | 
 |  | 
 |   size_t begin = offset_; | 
 |   while (offset_ < file_.size() && file_[offset_] != '\n') | 
 |     offset_++; | 
 |   line_number_++; | 
 |  | 
 |   *line = file_.substr(begin, offset_ - begin); | 
 |   *line_number = line_number_; | 
 |  | 
 |   // If we didn't hit EOF, skip past the newline for the next one. | 
 |   if (offset_ < file_.size()) | 
 |     offset_++; | 
 |   return true; | 
 | } |