|  | // 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 "gn/c_include_iterator.h" | 
|  |  | 
|  | #include <iterator> | 
|  |  | 
|  | #include "base/logging.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "gn/input_file.h" | 
|  | #include "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. | 
|  | std::string_view TrimLeadingWhitespace(const std::string_view& 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 std::string_view& 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 std::string_view& line, | 
|  | std::string_view* path, | 
|  | int* begin_char) { | 
|  | static const char kInclude[] = "include"; | 
|  | static const size_t kIncludeLen = std::size(kInclude) - 1;  // No null. | 
|  | static const char kImport[] = "import"; | 
|  | static const size_t kImportLen = std::size(kImport) - 1;  // No null. | 
|  |  | 
|  | std::string_view trimmed = TrimLeadingWhitespace(line); | 
|  | if (trimmed.empty()) | 
|  | return INCLUDE_NONE; | 
|  |  | 
|  | if (trimmed[0] != '#') | 
|  | return INCLUDE_NONE; | 
|  |  | 
|  | trimmed = TrimLeadingWhitespace(trimmed.substr(1)); | 
|  |  | 
|  | std::string_view contents; | 
|  | if (base::StartsWith(trimmed, std::string_view(kInclude, kIncludeLen), | 
|  | base::CompareCase::SENSITIVE)) | 
|  | contents = TrimLeadingWhitespace(trimmed.substr(kIncludeLen)); | 
|  | else if (base::StartsWith(trimmed, std::string_view(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 == std::string_view::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 std::string_view& line) { | 
|  | return line.find("nogncheck") != std::string_view::npos; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | const int CIncludeIterator::kMaxNonIncludeLines = 10; | 
|  |  | 
|  | CIncludeIterator::CIncludeIterator(const InputFile* input) | 
|  | : input_file_(input), file_(input->contents()) {} | 
|  |  | 
|  | CIncludeIterator::~CIncludeIterator() = default; | 
|  |  | 
|  | bool CIncludeIterator::GetNextIncludeString( | 
|  | IncludeStringWithLocation* include) { | 
|  | std::string_view line; | 
|  | int cur_line_number = 0; | 
|  | while (lines_since_last_include_ <= kMaxNonIncludeLines && | 
|  | GetNextLine(&line, &cur_line_number)) { | 
|  | std::string_view include_contents; | 
|  | int begin_char; | 
|  | IncludeType type = ExtractInclude(line, &include_contents, &begin_char); | 
|  | if (HasNoCheckAnnotation(line)) | 
|  | continue; | 
|  | if (type != INCLUDE_NONE) { | 
|  | include->contents = include_contents; | 
|  | include->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? */)); | 
|  | include->system_style_include = (type == INCLUDE_SYSTEM); | 
|  |  | 
|  | lines_since_last_include_ = 0; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (ShouldCountTowardNonIncludeLines(line)) | 
|  | lines_since_last_include_++; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool CIncludeIterator::GetNextLine(std::string_view* 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; | 
|  | } |