// 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(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(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; }