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.
5 #include "tools/gn/c_include_iterator.h"
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"
16 INCLUDE_SYSTEM
, // #include <...>
17 INCLUDE_USER
// #include "..."
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
;
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
) {
31 while (new_begin
< str
.size() &&
32 (str
[new_begin
] == ' ' || str
[new_begin
] == '\t'))
34 return str
.substr(new_begin
);
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.
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
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.
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.
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
,
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.
72 base::StringPiece trimmed
= TrimLeadingWhitespace(line
);
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
));
85 IncludeType type
= INCLUDE_NONE
;
86 char terminating_char
= 0;
87 if (contents
[0] == '"') {
89 terminating_char
= '"';
90 } else if (contents
[0] == '<') {
91 type
= INCLUDE_SYSTEM
;
92 terminating_char
= '>';
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
)
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;
108 // Returns true if this line has a "nogncheck" comment associated with it.
109 bool HasNoCheckAnnotation(const base::StringPiece
& line
) {
110 return line
.find("nogncheck") != base::StringPiece::npos
;
115 const int CIncludeIterator::kMaxNonIncludeLines
= 10;
117 CIncludeIterator::CIncludeIterator(const InputFile
* input
)
118 : input_file_(input
),
119 file_(input
->contents()),
122 lines_since_last_include_(0) {
125 CIncludeIterator::~CIncludeIterator() {
128 bool CIncludeIterator::GetNextIncludeString(base::StringPiece
* out
,
129 LocationRange
* location
) {
130 base::StringPiece line
;
131 int cur_line_number
= 0;
132 while (lines_since_last_include_
<= kMaxNonIncludeLines
&&
133 GetNextLine(&line
, &cur_line_number
)) {
134 base::StringPiece include_contents
;
136 IncludeType type
= ExtractInclude(line
, &include_contents
, &begin_char
);
137 if (type
== INCLUDE_USER
&& !HasNoCheckAnnotation(line
)) {
138 // Only count user includes for now.
139 *out
= include_contents
;
140 *location
= LocationRange(
141 Location(input_file_
,
144 -1 /* TODO(scottmg): Is this important? */),
145 Location(input_file_
,
147 begin_char
+ static_cast<int>(include_contents
.size()),
148 -1 /* TODO(scottmg): Is this important? */));
150 lines_since_last_include_
= 0;
154 if (ShouldCountTowardNonIncludeLines(line
))
155 lines_since_last_include_
++;
160 bool CIncludeIterator::GetNextLine(base::StringPiece
* line
, int* line_number
) {
161 if (offset_
== file_
.size())
164 size_t begin
= offset_
;
165 while (offset_
< file_
.size() && file_
[offset_
] != '\n')
169 *line
= file_
.substr(begin
, offset_
- begin
);
170 *line_number
= line_number_
;
172 // If we didn't hit EOF, skip past the newline for the next one.
173 if (offset_
< file_
.size())