Home | History | Annotate | Download | only in gn
      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