• 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 "gn/c_include_iterator.h"
6 
7 #include <iterator>
8 
9 #include "base/logging.h"
10 #include "base/macros.h"
11 #include "base/strings/string_util.h"
12 #include "gn/input_file.h"
13 #include "gn/location.h"
14 
15 namespace {
16 
17 enum IncludeType {
18   INCLUDE_NONE,
19   INCLUDE_SYSTEM,  // #include <...>
20   INCLUDE_USER     // #include "..."
21 };
22 
23 // Returns a new string piece referencing the same buffer as the argument, but
24 // with leading space trimmed. This only checks for space and tab characters
25 // since we're dealing with lines in C source files.
TrimLeadingWhitespace(const std::string_view & str)26 std::string_view TrimLeadingWhitespace(const std::string_view& str) {
27   size_t new_begin = 0;
28   while (new_begin < str.size() &&
29          (str[new_begin] == ' ' || str[new_begin] == '\t'))
30     new_begin++;
31   return str.substr(new_begin);
32 }
33 
34 // We don't want to count comment lines and preprocessor lines toward our
35 // "max lines to look at before giving up" since the beginnings of some files
36 // may have a lot of comments.
37 //
38 // We only handle C-style "//" comments since this is the normal commenting
39 // style used in Chrome, and do so pretty stupidly. We don't want to write a
40 // full C++ parser here, we're just trying to get a good heuristic for checking
41 // the file.
42 //
43 // We assume the line has leading whitespace trimmed. We also assume that empty
44 // lines have already been filtered out.
ShouldCountTowardNonIncludeLines(const std::string_view & line)45 bool ShouldCountTowardNonIncludeLines(const std::string_view& line) {
46   if (base::StartsWith(line, "//", base::CompareCase::SENSITIVE))
47     return false;  // Don't count comments.
48   if (base::StartsWith(line, "/*", base::CompareCase::SENSITIVE) ||
49       base::StartsWith(line, " *", base::CompareCase::SENSITIVE))
50     return false;  // C-style comment blocks with stars along the left side.
51   if (base::StartsWith(line, "#", base::CompareCase::SENSITIVE))
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 std::string_view & line,std::string_view * path,int * begin_char)64 IncludeType ExtractInclude(const std::string_view& line,
65                            std::string_view* path,
66                            int* begin_char) {
67   static const char kInclude[] = "include";
68   static const size_t kIncludeLen = std::size(kInclude) - 1;  // No null.
69   static const char kImport[] = "import";
70   static const size_t kImportLen = std::size(kImport) - 1;  // No null.
71 
72   std::string_view trimmed = TrimLeadingWhitespace(line);
73   if (trimmed.empty())
74     return INCLUDE_NONE;
75 
76   if (trimmed[0] != '#')
77     return INCLUDE_NONE;
78 
79   trimmed = TrimLeadingWhitespace(trimmed.substr(1));
80 
81   std::string_view contents;
82   if (base::StartsWith(trimmed, std::string_view(kInclude, kIncludeLen),
83                        base::CompareCase::SENSITIVE))
84     contents = TrimLeadingWhitespace(trimmed.substr(kIncludeLen));
85   else if (base::StartsWith(trimmed, std::string_view(kImport, kImportLen),
86                             base::CompareCase::SENSITIVE))
87     contents = TrimLeadingWhitespace(trimmed.substr(kImportLen));
88 
89   if (contents.empty())
90     return INCLUDE_NONE;
91 
92   IncludeType type = INCLUDE_NONE;
93   char terminating_char = 0;
94   if (contents[0] == '"') {
95     type = INCLUDE_USER;
96     terminating_char = '"';
97   } else if (contents[0] == '<') {
98     type = INCLUDE_SYSTEM;
99     terminating_char = '>';
100   } else {
101     return INCLUDE_NONE;
102   }
103 
104   // Count everything to next "/> as the contents.
105   size_t terminator_index = contents.find(terminating_char, 1);
106   if (terminator_index == std::string_view::npos)
107     return INCLUDE_NONE;
108 
109   *path = contents.substr(1, terminator_index - 1);
110   // Note: one based so we do "+ 1".
111   *begin_char = static_cast<int>(path->data() - line.data()) + 1;
112   return type;
113 }
114 
115 // Returns true if this line has a "nogncheck" comment associated with it.
HasNoCheckAnnotation(const std::string_view & line)116 bool HasNoCheckAnnotation(const std::string_view& line) {
117   return line.find("nogncheck") != std::string_view::npos;
118 }
119 
120 }  // namespace
121 
122 const int CIncludeIterator::kMaxNonIncludeLines = 10;
123 
CIncludeIterator(const InputFile * input)124 CIncludeIterator::CIncludeIterator(const InputFile* input)
125     : input_file_(input), file_(input->contents()) {}
126 
127 CIncludeIterator::~CIncludeIterator() = default;
128 
GetNextIncludeString(IncludeStringWithLocation * include)129 bool CIncludeIterator::GetNextIncludeString(
130     IncludeStringWithLocation* include) {
131   std::string_view line;
132   int cur_line_number = 0;
133   while (lines_since_last_include_ <= kMaxNonIncludeLines &&
134          GetNextLine(&line, &cur_line_number)) {
135     std::string_view include_contents;
136     int begin_char;
137     IncludeType type = ExtractInclude(line, &include_contents, &begin_char);
138     if (HasNoCheckAnnotation(line))
139       continue;
140     if (type != INCLUDE_NONE) {
141       include->contents = include_contents;
142       include->location = LocationRange(
143           Location(input_file_, cur_line_number, begin_char,
144                    -1 /* TODO(scottmg): Is this important? */),
145           Location(input_file_, cur_line_number,
146                    begin_char + static_cast<int>(include_contents.size()),
147                    -1 /* TODO(scottmg): Is this important? */));
148       include->system_style_include = (type == INCLUDE_SYSTEM);
149 
150       lines_since_last_include_ = 0;
151       return true;
152     }
153 
154     if (ShouldCountTowardNonIncludeLines(line))
155       lines_since_last_include_++;
156   }
157   return false;
158 }
159 
GetNextLine(std::string_view * line,int * line_number)160 bool CIncludeIterator::GetNextLine(std::string_view* line, int* line_number) {
161   if (offset_ == file_.size())
162     return false;
163 
164   size_t begin = offset_;
165   while (offset_ < file_.size() && file_[offset_] != '\n')
166     offset_++;
167   line_number_++;
168 
169   *line = file_.substr(begin, offset_ - begin);
170   *line_number = line_number_;
171 
172   // If we didn't hit EOF, skip past the newline for the next one.
173   if (offset_ < file_.size())
174     offset_++;
175   return true;
176 }
177