• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
StartsWith(const base::StringPiece & str,const base::StringPiece & 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.
TrimLeadingWhitespace(const base::StringPiece & str)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.
ShouldCountTowardNonIncludeLines(const base::StringPiece & line)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.
ExtractInclude(const base::StringPiece & line,base::StringPiece * path,int * 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 
CIncludeIterator(const InputFile * input)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 
~CIncludeIterator()120 CIncludeIterator::~CIncludeIterator() {
121 }
122 
GetNextIncludeString(base::StringPiece * out,LocationRange * location)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 
GetNextLine(base::StringPiece * line,int * line_number)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