1 |
|
#!/usr/bin/env python
|
2 |
|
#
|
3 |
|
# Copyright (c) 2009 Google Inc. All rights reserved.
|
4 |
|
#
|
5 |
|
# Redistribution and use in source and binary forms, with or without
|
6 |
|
# modification, are permitted provided that the following conditions are
|
7 |
|
# met:
|
8 |
|
#
|
9 |
|
# * Redistributions of source code must retain the above copyright
|
10 |
|
# notice, this list of conditions and the following disclaimer.
|
11 |
|
# * Redistributions in binary form must reproduce the above
|
12 |
|
# copyright notice, this list of conditions and the following disclaimer
|
13 |
|
# in the documentation and/or other materials provided with the
|
14 |
|
# distribution.
|
15 |
|
# * Neither the name of Google Inc. nor the names of its
|
16 |
|
# contributors may be used to endorse or promote products derived from
|
17 |
|
# this software without specific prior written permission.
|
18 |
|
#
|
19 |
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
20 |
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
21 |
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
22 |
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
23 |
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
24 |
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
25 |
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
26 |
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
27 |
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
28 |
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
29 |
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
30 |
|
|
31 |
|
"""Does google-lint on c++ files.
|
32 |
|
|
33 |
|
The goal of this script is to identify places in the code that *may*
|
34 |
|
be in non-compliance with google style. It does not attempt to fix
|
35 |
|
up these problems -- the point is to educate. It does also not
|
36 |
|
attempt to find all problems, or to ensure that everything it does
|
37 |
|
find is legitimately a problem.
|
38 |
|
|
39 |
|
In particular, we can get very confused by /* and // inside strings!
|
40 |
|
We do a small hack, which is to ignore //'s with "'s after them on the
|
41 |
|
same line, but it is far from perfect (in either direction).
|
42 |
|
"""
|
43 |
|
|
44 |
|
import codecs
|
45 |
|
import copy
|
46 |
|
import getopt
|
47 |
|
import math # for log
|
48 |
|
import os
|
49 |
|
import re
|
50 |
|
import sre_compile
|
51 |
|
import string
|
52 |
|
import sys
|
53 |
|
import unicodedata
|
54 |
|
|
55 |
|
|
56 |
|
_USAGE = """
|
57 |
|
Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...]
|
58 |
|
[--counting=total|toplevel|detailed] [--root=subdir]
|
59 |
|
[--linelength=digits]
|
60 |
|
<file> [file] ...
|
61 |
|
|
62 |
|
The style guidelines this tries to follow are those in
|
63 |
|
http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml
|
64 |
|
|
65 |
|
Every problem is given a confidence score from 1-5, with 5 meaning we are
|
66 |
|
certain of the problem, and 1 meaning it could be a legitimate construct.
|
67 |
|
This will miss some errors, and is not a substitute for a code review.
|
68 |
|
|
69 |
|
To suppress false-positive errors of a certain category, add a
|
70 |
|
'NOLINT(category)' comment to the line. NOLINT or NOLINT(*)
|
71 |
|
suppresses errors of all categories on that line.
|
72 |
|
|
73 |
|
The files passed in will be linted; at least one file must be provided.
|
74 |
|
Default linted extensions are .cc, .cpp, .cu, .cuh and .h. Change the
|
75 |
|
extensions with the --extensions flag.
|
76 |
|
|
77 |
|
Flags:
|
78 |
|
|
79 |
|
output=vs7
|
80 |
|
By default, the output is formatted to ease emacs parsing. Visual Studio
|
81 |
|
compatible output (vs7) may also be used. Other formats are unsupported.
|
82 |
|
|
83 |
|
verbose=#
|
84 |
|
Specify a number 0-5 to restrict errors to certain verbosity levels.
|
85 |
|
|
86 |
|
filter=-x,+y,...
|
87 |
|
Specify a comma-separated list of category-filters to apply: only
|
88 |
|
error messages whose category names pass the filters will be printed.
|
89 |
|
(Category names are printed with the message and look like
|
90 |
|
"[whitespace/indent]".) Filters are evaluated left to right.
|
91 |
|
"-FOO" and "FOO" means "do not print categories that start with FOO".
|
92 |
|
"+FOO" means "do print categories that start with FOO".
|
93 |
|
|
94 |
|
Examples: --filter=-whitespace,+whitespace/braces
|
95 |
|
--filter=whitespace,runtime/printf,+runtime/printf_format
|
96 |
|
--filter=-,+build/include_what_you_use
|
97 |
|
|
98 |
|
To see a list of all the categories used in cpplint, pass no arg:
|
99 |
|
--filter=
|
100 |
|
|
101 |
|
counting=total|toplevel|detailed
|
102 |
|
The total number of errors found is always printed. If
|
103 |
|
'toplevel' is provided, then the count of errors in each of
|
104 |
|
the top-level categories like 'build' and 'whitespace' will
|
105 |
|
also be printed. If 'detailed' is provided, then a count
|
106 |
|
is provided for each category like 'build/class'.
|
107 |
|
|
108 |
|
root=subdir
|
109 |
|
The root directory used for deriving header guard CPP variable.
|
110 |
|
By default, the header guard CPP variable is calculated as the relative
|
111 |
|
path to the directory that contains .git, .hg, or .svn. When this flag
|
112 |
|
is specified, the relative path is calculated from the specified
|
113 |
|
directory. If the specified directory does not exist, this flag is
|
114 |
|
ignored.
|
115 |
|
|
116 |
|
Examples:
|
117 |
|
Assuming that src/.git exists, the header guard CPP variables for
|
118 |
|
src/chrome/browser/ui/browser.h are:
|
119 |
|
|
120 |
|
No flag => CHROME_BROWSER_UI_BROWSER_H_
|
121 |
|
--root=chrome => BROWSER_UI_BROWSER_H_
|
122 |
|
--root=chrome/browser => UI_BROWSER_H_
|
123 |
|
|
124 |
|
linelength=digits
|
125 |
|
This is the allowed line length for the project. The default value is
|
126 |
|
80 characters.
|
127 |
|
|
128 |
|
Examples:
|
129 |
|
--linelength=120
|
130 |
|
|
131 |
|
extensions=extension,extension,...
|
132 |
|
The allowed file extensions that cpplint will check
|
133 |
|
|
134 |
|
Examples:
|
135 |
|
--extensions=hpp,cpp
|
136 |
|
|
137 |
|
cpplint.py supports per-directory configurations specified in CPPLINT.cfg
|
138 |
|
files. CPPLINT.cfg file can contain a number of key=value pairs.
|
139 |
|
Currently the following options are supported:
|
140 |
|
|
141 |
|
set noparent
|
142 |
|
filter=+filter1,-filter2,...
|
143 |
|
exclude_files=regex
|
144 |
|
linelength=80
|
145 |
|
|
146 |
|
"set noparent" option prevents cpplint from traversing directory tree
|
147 |
|
upwards looking for more .cfg files in parent directories. This option
|
148 |
|
is usually placed in the top-level project directory.
|
149 |
|
|
150 |
|
The "filter" option is similar in function to --filter flag. It specifies
|
151 |
|
message filters in addition to the |_DEFAULT_FILTERS| and those specified
|
152 |
|
through --filter command-line flag.
|
153 |
|
|
154 |
|
"exclude_files" allows to specify a regular expression to be matched against
|
155 |
|
a file name. If the expression matches, the file is skipped and not run
|
156 |
|
through liner.
|
157 |
|
|
158 |
|
"linelength" allows to specify the allowed line length for the project.
|
159 |
|
|
160 |
|
CPPLINT.cfg has an effect on files in the same directory and all
|
161 |
|
sub-directories, unless overridden by a nested configuration file.
|
162 |
|
|
163 |
|
Example file:
|
164 |
|
filter=-build/include_order,+build/include_alpha
|
165 |
|
exclude_files=.*\.cc
|
166 |
|
|
167 |
|
The above example disables build/include_order warning and enables
|
168 |
|
build/include_alpha as well as excludes all .cc from being
|
169 |
|
processed by linter, in the current directory (where the .cfg
|
170 |
|
file is located) and all sub-directories.
|
171 |
|
"""
|
172 |
|
|
173 |
|
# We categorize each error message we print. Here are the categories.
|
174 |
|
# We want an explicit list so we can list them all in cpplint --filter=.
|
175 |
|
# If you add a new error message with a new category, add it to the list
|
176 |
|
# here! cpplint_unittest.py should tell you if you forget to do this.
|
177 |
|
_ERROR_CATEGORIES = [
|
178 |
|
'build/class',
|
179 |
|
'build/c++11',
|
180 |
|
'build/deprecated',
|
181 |
|
'build/endif_comment',
|
182 |
|
'build/explicit_make_pair',
|
183 |
|
'build/forward_decl',
|
184 |
|
'build/header_guard',
|
185 |
|
'build/include',
|
186 |
|
'build/include_alpha',
|
187 |
|
'build/include_order',
|
188 |
|
'build/include_what_you_use',
|
189 |
|
'build/namespaces',
|
190 |
|
'build/printf_format',
|
191 |
|
'build/storage_class',
|
192 |
|
'legal/copyright',
|
193 |
|
'readability/alt_tokens',
|
194 |
|
'readability/braces',
|
195 |
|
'readability/casting',
|
196 |
|
'readability/check',
|
197 |
|
'readability/constructors',
|
198 |
|
'readability/fn_size',
|
199 |
|
'readability/function',
|
200 |
|
'readability/inheritance',
|
201 |
|
'readability/multiline_comment',
|
202 |
|
'readability/multiline_string',
|
203 |
|
'readability/namespace',
|
204 |
|
'readability/nolint',
|
205 |
|
'readability/nul',
|
206 |
|
'readability/strings',
|
207 |
|
'readability/todo',
|
208 |
|
'readability/utf8',
|
209 |
|
'runtime/arrays',
|
210 |
|
'runtime/casting',
|
211 |
|
'runtime/explicit',
|
212 |
|
'runtime/int',
|
213 |
|
'runtime/init',
|
214 |
|
'runtime/invalid_increment',
|
215 |
|
'runtime/member_string_references',
|
216 |
|
'runtime/memset',
|
217 |
|
'runtime/indentation_namespace',
|
218 |
|
'runtime/operator',
|
219 |
|
'runtime/printf',
|
220 |
|
'runtime/printf_format',
|
221 |
|
'runtime/references',
|
222 |
|
'runtime/string',
|
223 |
|
'runtime/threadsafe_fn',
|
224 |
|
'runtime/vlog',
|
225 |
|
'whitespace/blank_line',
|
226 |
|
'whitespace/braces',
|
227 |
|
'whitespace/comma',
|
228 |
|
'whitespace/comments',
|
229 |
|
'whitespace/empty_conditional_body',
|
230 |
|
'whitespace/empty_loop_body',
|
231 |
|
'whitespace/end_of_line',
|
232 |
|
'whitespace/ending_newline',
|
233 |
|
'whitespace/forcolon',
|
234 |
|
'whitespace/indent',
|
235 |
|
'whitespace/line_length',
|
236 |
|
'whitespace/newline',
|
237 |
|
'whitespace/operators',
|
238 |
|
'whitespace/parens',
|
239 |
|
'whitespace/semicolon',
|
240 |
|
'whitespace/tab',
|
241 |
|
'whitespace/todo',
|
242 |
|
]
|
243 |
|
|
244 |
|
# These error categories are no longer enforced by cpplint, but for backwards-
|
245 |
|
# compatibility they may still appear in NOLINT comments.
|
246 |
|
_LEGACY_ERROR_CATEGORIES = [
|
247 |
|
'readability/streams',
|
248 |
|
]
|
249 |
|
|
250 |
|
# The default state of the category filter. This is overridden by the --filter=
|
251 |
|
# flag. By default all errors are on, so only add here categories that should be
|
252 |
|
# off by default (i.e., categories that must be enabled by the --filter= flags).
|
253 |
|
# All entries here should start with a '-' or '+', as in the --filter= flag.
|
254 |
|
_DEFAULT_FILTERS = ['-build/include_alpha']
|
255 |
|
|
256 |
|
# We used to check for high-bit characters, but after much discussion we
|
257 |
|
# decided those were OK, as long as they were in UTF-8 and didn't represent
|
258 |
|
# hard-coded international strings, which belong in a separate i18n file.
|
259 |
|
|
260 |
|
# C++ headers
|
261 |
|
_CPP_HEADERS = frozenset([
|
262 |
|
# Legacy
|
263 |
|
'algobase.h',
|
264 |
|
'algo.h',
|
265 |
|
'alloc.h',
|
266 |
|
'builtinbuf.h',
|
267 |
|
'bvector.h',
|
268 |
|
'complex.h',
|
269 |
|
'defalloc.h',
|
270 |
|
'deque.h',
|
271 |
|
'editbuf.h',
|
272 |
|
'fstream.h',
|
273 |
|
'function.h',
|
274 |
|
'hash_map',
|
275 |
|
'hash_map.h',
|
276 |
|
'hash_set',
|
277 |
|
'hash_set.h',
|
278 |
|
'hashtable.h',
|
279 |
|
'heap.h',
|
280 |
|
'indstream.h',
|
281 |
|
'iomanip.h',
|
282 |
|
'iostream.h',
|
283 |
|
'istream.h',
|
284 |
|
'iterator.h',
|
285 |
|
'list.h',
|
286 |
|
'map.h',
|
287 |
|
'multimap.h',
|
288 |
|
'multiset.h',
|
289 |
|
'ostream.h',
|
290 |
|
'pair.h',
|
291 |
|
'parsestream.h',
|
292 |
|
'pfstream.h',
|
293 |
|
'procbuf.h',
|
294 |
|
'pthread_alloc',
|
295 |
|
'pthread_alloc.h',
|
296 |
|
'rope',
|
297 |
|
'rope.h',
|
298 |
|
'ropeimpl.h',
|
299 |
|
'set.h',
|
300 |
|
'slist',
|
301 |
|
'slist.h',
|
302 |
|
'stack.h',
|
303 |
|
'stdiostream.h',
|
304 |
|
'stl_alloc.h',
|
305 |
|
'stl_relops.h',
|
306 |
|
'streambuf.h',
|
307 |
|
'stream.h',
|
308 |
|
'strfile.h',
|
309 |
|
'strstream.h',
|
310 |
|
'tempbuf.h',
|
311 |
|
'tree.h',
|
312 |
|
'type_traits.h',
|
313 |
|
'vector.h',
|
314 |
|
# 17.6.1.2 C++ library headers
|
315 |
|
'algorithm',
|
316 |
|
'array',
|
317 |
|
'atomic',
|
318 |
|
'bitset',
|
319 |
|
'chrono',
|
320 |
|
'codecvt',
|
321 |
|
'complex',
|
322 |
|
'condition_variable',
|
323 |
|
'deque',
|
324 |
|
'exception',
|
325 |
|
'forward_list',
|
326 |
|
'fstream',
|
327 |
|
'functional',
|
328 |
|
'future',
|
329 |
|
'initializer_list',
|
330 |
|
'iomanip',
|
331 |
|
'ios',
|
332 |
|
'iosfwd',
|
333 |
|
'iostream',
|
334 |
|
'istream',
|
335 |
|
'iterator',
|
336 |
|
'limits',
|
337 |
|
'list',
|
338 |
|
'locale',
|
339 |
|
'map',
|
340 |
|
'memory',
|
341 |
|
'mutex',
|
342 |
|
'new',
|
343 |
|
'numeric',
|
344 |
|
'ostream',
|
345 |
|
'queue',
|
346 |
|
'random',
|
347 |
|
'ratio',
|
348 |
|
'regex',
|
349 |
|
'set',
|
350 |
|
'sstream',
|
351 |
|
'stack',
|
352 |
|
'stdexcept',
|
353 |
|
'streambuf',
|
354 |
|
'string',
|
355 |
|
'strstream',
|
356 |
|
'system_error',
|
357 |
|
'thread',
|
358 |
|
'tuple',
|
359 |
|
'typeindex',
|
360 |
|
'typeinfo',
|
361 |
|
'type_traits',
|
362 |
|
'unordered_map',
|
363 |
|
'unordered_set',
|
364 |
|
'utility',
|
365 |
|
'valarray',
|
366 |
|
'vector',
|
367 |
|
# 17.6.1.2 C++ headers for C library facilities
|
368 |
|
'cassert',
|
369 |
|
'ccomplex',
|
370 |
|
'cctype',
|
371 |
|
'cerrno',
|
372 |
|
'cfenv',
|
373 |
|
'cfloat',
|
374 |
|
'cinttypes',
|
375 |
|
'ciso646',
|
376 |
|
'climits',
|
377 |
|
'clocale',
|
378 |
|
'cmath',
|
379 |
|
'csetjmp',
|
380 |
|
'csignal',
|
381 |
|
'cstdalign',
|
382 |
|
'cstdarg',
|
383 |
|
'cstdbool',
|
384 |
|
'cstddef',
|
385 |
|
'cstdint',
|
386 |
|
'cstdio',
|
387 |
|
'cstdlib',
|
388 |
|
'cstring',
|
389 |
|
'ctgmath',
|
390 |
|
'ctime',
|
391 |
|
'cuchar',
|
392 |
|
'cwchar',
|
393 |
|
'cwctype',
|
394 |
|
])
|
395 |
|
|
396 |
|
|
397 |
|
# These headers are excluded from [build/include] and [build/include_order]
|
398 |
|
# checks:
|
399 |
|
# - Anything not following google file name conventions (containing an
|
400 |
|
# uppercase character, such as Python.h or nsStringAPI.h, for example).
|
401 |
|
# - Lua headers.
|
402 |
|
_THIRD_PARTY_HEADERS_PATTERN = re.compile(
|
403 |
|
r'^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$')
|
404 |
|
|
405 |
|
|
406 |
|
# Assertion macros. These are defined in base/logging.h and
|
407 |
|
# testing/base/gunit.h. Note that the _M versions need to come first
|
408 |
|
# for substring matching to work.
|
409 |
|
_CHECK_MACROS = [
|
410 |
|
'DCHECK', 'CHECK',
|
411 |
|
'EXPECT_TRUE_M', 'EXPECT_TRUE',
|
412 |
|
'ASSERT_TRUE_M', 'ASSERT_TRUE',
|
413 |
|
'EXPECT_FALSE_M', 'EXPECT_FALSE',
|
414 |
|
'ASSERT_FALSE_M', 'ASSERT_FALSE',
|
415 |
|
]
|
416 |
|
|
417 |
|
# Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE
|
418 |
|
_CHECK_REPLACEMENT = dict([(m, {}) for m in _CHECK_MACROS])
|
419 |
|
|
420 |
|
for op, replacement in [('==', 'EQ'), ('!=', 'NE'),
|
421 |
|
('>=', 'GE'), ('>', 'GT'),
|
422 |
|
('<=', 'LE'), ('<', 'LT')]:
|
423 |
|
_CHECK_REPLACEMENT['DCHECK'][op] = 'DCHECK_%s' % replacement
|
424 |
|
_CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement
|
425 |
|
_CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement
|
426 |
|
_CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement
|
427 |
|
_CHECK_REPLACEMENT['EXPECT_TRUE_M'][op] = 'EXPECT_%s_M' % replacement
|
428 |
|
_CHECK_REPLACEMENT['ASSERT_TRUE_M'][op] = 'ASSERT_%s_M' % replacement
|
429 |
|
|
430 |
|
for op, inv_replacement in [('==', 'NE'), ('!=', 'EQ'),
|
431 |
|
('>=', 'LT'), ('>', 'LE'),
|
432 |
|
('<=', 'GT'), ('<', 'GE')]:
|
433 |
|
_CHECK_REPLACEMENT['EXPECT_FALSE'][op] = 'EXPECT_%s' % inv_replacement
|
434 |
|
_CHECK_REPLACEMENT['ASSERT_FALSE'][op] = 'ASSERT_%s' % inv_replacement
|
435 |
|
_CHECK_REPLACEMENT['EXPECT_FALSE_M'][op] = 'EXPECT_%s_M' % inv_replacement
|
436 |
|
_CHECK_REPLACEMENT['ASSERT_FALSE_M'][op] = 'ASSERT_%s_M' % inv_replacement
|
437 |
|
|
438 |
|
# Alternative tokens and their replacements. For full list, see section 2.5
|
439 |
|
# Alternative tokens [lex.digraph] in the C++ standard.
|
440 |
|
#
|
441 |
|
# Digraphs (such as '%:') are not included here since it's a mess to
|
442 |
|
# match those on a word boundary.
|
443 |
|
_ALT_TOKEN_REPLACEMENT = {
|
444 |
|
'and': '&&',
|
445 |
|
'bitor': '|',
|
446 |
|
'or': '||',
|
447 |
|
'xor': '^',
|
448 |
|
'compl': '~',
|
449 |
|
'bitand': '&',
|
450 |
|
'and_eq': '&=',
|
451 |
|
'or_eq': '|=',
|
452 |
|
'xor_eq': '^=',
|
453 |
|
'not': '!',
|
454 |
|
'not_eq': '!='
|
455 |
|
}
|
456 |
|
|
457 |
|
# Compile regular expression that matches all the above keywords. The "[ =()]"
|
458 |
|
# bit is meant to avoid matching these keywords outside of boolean expressions.
|
459 |
|
#
|
460 |
|
# False positives include C-style multi-line comments and multi-line strings
|
461 |
|
# but those have always been troublesome for cpplint.
|
462 |
|
_ALT_TOKEN_REPLACEMENT_PATTERN = re.compile(
|
463 |
|
r'[ =()](' + ('|'.join(_ALT_TOKEN_REPLACEMENT.keys())) + r')(?=[ (]|$)')
|
464 |
|
|
465 |
|
|
466 |
|
# These constants define types of headers for use with
|
467 |
|
# _IncludeState.CheckNextIncludeOrder().
|
468 |
|
_C_SYS_HEADER = 1
|
469 |
|
_CPP_SYS_HEADER = 2
|
470 |
|
_LIKELY_MY_HEADER = 3
|
471 |
|
_POSSIBLE_MY_HEADER = 4
|
472 |
|
_OTHER_HEADER = 5
|
473 |
|
|
474 |
|
# These constants define the current inline assembly state
|
475 |
|
_NO_ASM = 0 # Outside of inline assembly block
|
476 |
|
_INSIDE_ASM = 1 # Inside inline assembly block
|
477 |
|
_END_ASM = 2 # Last line of inline assembly block
|
478 |
|
_BLOCK_ASM = 3 # The whole block is an inline assembly block
|
479 |
|
|
480 |
|
# Match start of assembly blocks
|
481 |
|
_MATCH_ASM = re.compile(r'^\s*(?:asm|_asm|__asm|__asm__)'
|
482 |
|
r'(?:\s+(volatile|__volatile__))?'
|
483 |
|
r'\s*[{(]')
|
484 |
|
|
485 |
|
|
486 |
|
_regexp_compile_cache = {}
|
487 |
|
|
488 |
|
# {str, set(int)}: a map from error categories to sets of linenumbers
|
489 |
|
# on which those errors are expected and should be suppressed.
|
490 |
|
_error_suppressions = {}
|
491 |
|
|
492 |
|
# The root directory used for deriving header guard CPP variable.
|
493 |
|
# This is set by --root flag.
|
494 |
|
_root = None
|
495 |
|
|
496 |
|
# The allowed line length of files.
|
497 |
|
# This is set by --linelength flag.
|
498 |
|
_line_length = 80
|
499 |
|
|
500 |
|
# The allowed extensions for file names
|
501 |
|
# This is set by --extensions flag.
|
502 |
|
_valid_extensions = set(['cc', 'h', 'cpp', 'cu', 'cuh'])
|
503 |
|
|
504 |
|
def ParseNolintSuppressions(filename, raw_line, linenum, error):
|
505 |
|
"""Updates the global list of error-suppressions.
|
506 |
|
|
507 |
|
Parses any NOLINT comments on the current line, updating the global
|
508 |
|
error_suppressions store. Reports an error if the NOLINT comment
|
509 |
|
was malformed.
|
510 |
|
|
511 |
|
Args:
|
512 |
|
filename: str, the name of the input file.
|
513 |
|
raw_line: str, the line of input text, with comments.
|
514 |
|
linenum: int, the number of the current line.
|
515 |
|
error: function, an error handler.
|
516 |
|
"""
|
517 |
|
matched = Search(r'\bNOLINT(NEXTLINE)?\b(\([^)]+\))?', raw_line)
|
518 |
|
if matched:
|
519 |
|
if matched.group(1):
|
520 |
|
suppressed_line = linenum + 1
|
521 |
|
else:
|
522 |
|
suppressed_line = linenum
|
523 |
|
category = matched.group(2)
|
524 |
|
if category in (None, '(*)'): # => "suppress all"
|
525 |
|
_error_suppressions.setdefault(None, set()).add(suppressed_line)
|
526 |
|
else:
|
527 |
|
if category.startswith('(') and category.endswith(')'):
|
528 |
|
category = category[1:-1]
|
529 |
|
if category in _ERROR_CATEGORIES:
|
530 |
|
_error_suppressions.setdefault(category, set()).add(suppressed_line)
|
531 |
|
elif category not in _LEGACY_ERROR_CATEGORIES:
|
532 |
|
error(filename, linenum, 'readability/nolint', 5,
|
533 |
|
'Unknown NOLINT error category: %s' % category)
|
534 |
|
|
535 |
|
|
536 |
|
def ResetNolintSuppressions():
|
537 |
|
"""Resets the set of NOLINT suppressions to empty."""
|
538 |
|
_error_suppressions.clear()
|
539 |
|
|
540 |
|
|
541 |
|
def IsErrorSuppressedByNolint(category, linenum):
|
542 |
|
"""Returns true if the specified error category is suppressed on this line.
|
543 |
|
|
544 |
|
Consults the global error_suppressions map populated by
|
545 |
|
ParseNolintSuppressions/ResetNolintSuppressions.
|
546 |
|
|
547 |
|
Args:
|
548 |
|
category: str, the category of the error.
|
549 |
|
linenum: int, the current line number.
|
550 |
|
Returns:
|
551 |
|
bool, True iff the error should be suppressed due to a NOLINT comment.
|
552 |
|
"""
|
553 |
|
return (linenum in _error_suppressions.get(category, set()) or
|
554 |
|
linenum in _error_suppressions.get(None, set()))
|
555 |
|
|
556 |
|
|
557 |
|
def Match(pattern, s):
|
558 |
|
"""Matches the string with the pattern, caching the compiled regexp."""
|
559 |
|
# The regexp compilation caching is inlined in both Match and Search for
|
560 |
|
# performance reasons; factoring it out into a separate function turns out
|
561 |
|
# to be noticeably expensive.
|
562 |
|
if pattern not in _regexp_compile_cache:
|
563 |
|
_regexp_compile_cache[pattern] = sre_compile.compile(pattern)
|
564 |
|
return _regexp_compile_cache[pattern].match(s)
|
565 |
|
|
566 |
|
|
567 |
|
def ReplaceAll(pattern, rep, s):
|
568 |
|
"""Replaces instances of pattern in a string with a replacement.
|
569 |
|
|
570 |
|
The compiled regex is kept in a cache shared by Match and Search.
|
571 |
|
|
572 |
|
Args:
|
573 |
|
pattern: regex pattern
|
574 |
|
rep: replacement text
|
575 |
|
s: search string
|
576 |
|
|
577 |
|
Returns:
|
578 |
|
string with replacements made (or original string if no replacements)
|
579 |
|
"""
|
580 |
|
if pattern not in _regexp_compile_cache:
|
581 |
|
_regexp_compile_cache[pattern] = sre_compile.compile(pattern)
|
582 |
|
return _regexp_compile_cache[pattern].sub(rep, s)
|
583 |
|
|
584 |
|
|
585 |
|
def Search(pattern, s):
|
586 |
|
"""Searches the string for the pattern, caching the compiled regexp."""
|
587 |
|
if pattern not in _regexp_compile_cache:
|
588 |
|
_regexp_compile_cache[pattern] = sre_compile.compile(pattern)
|
589 |
|
return _regexp_compile_cache[pattern].search(s)
|
590 |
|
|
591 |
|
|
592 |
|
class _IncludeState(object):
|
593 |
|
"""Tracks line numbers for includes, and the order in which includes appear.
|
594 |
|
|
595 |
|
include_list contains list of lists of (header, line number) pairs.
|
596 |
|
It's a lists of lists rather than just one flat list to make it
|
597 |
|
easier to update across preprocessor boundaries.
|
598 |
|
|
599 |
|
Call CheckNextIncludeOrder() once for each header in the file, passing
|
600 |
|
in the type constants defined above. Calls in an illegal order will
|
601 |
|
raise an _IncludeError with an appropriate error message.
|
602 |
|
|
603 |
|
"""
|
604 |
|
# self._section will move monotonically through this set. If it ever
|
605 |
|
# needs to move backwards, CheckNextIncludeOrder will raise an error.
|
606 |
|
_INITIAL_SECTION = 0
|
607 |
|
_MY_H_SECTION = 1
|
608 |
|
_C_SECTION = 2
|
609 |
|
_CPP_SECTION = 3
|
610 |
|
_OTHER_H_SECTION = 4
|
611 |
|
|
612 |
|
_TYPE_NAMES = {
|
613 |
|
_C_SYS_HEADER: 'C system header',
|
614 |
|
_CPP_SYS_HEADER: 'C++ system header',
|
615 |
|
_LIKELY_MY_HEADER: 'header this file implements',
|
616 |
|
_POSSIBLE_MY_HEADER: 'header this file may implement',
|
617 |
|
_OTHER_HEADER: 'other header',
|
618 |
|
}
|
619 |
|
_SECTION_NAMES = {
|
620 |
|
_INITIAL_SECTION: "... nothing. (This can't be an error.)",
|
621 |
|
_MY_H_SECTION: 'a header this file implements',
|
622 |
|
_C_SECTION: 'C system header',
|
623 |
|
_CPP_SECTION: 'C++ system header',
|
624 |
|
_OTHER_H_SECTION: 'other header',
|
625 |
|
}
|
626 |
|
|
627 |
|
def __init__(self):
|
628 |
|
self.include_list = [[]]
|
629 |
|
self.ResetSection('')
|
630 |
|
|
631 |
|
def FindHeader(self, header):
|
632 |
|
"""Check if a header has already been included.
|
633 |
|
|
634 |
|
Args:
|
635 |
|
header: header to check.
|
636 |
|
Returns:
|
637 |
|
Line number of previous occurrence, or -1 if the header has not
|
638 |
|
been seen before.
|
639 |
|
"""
|
640 |
|
for section_list in self.include_list:
|
641 |
|
for f in section_list:
|
642 |
|
if f[0] == header:
|
643 |
|
return f[1]
|
644 |
|
return -1
|
645 |
|
|
646 |
|
def ResetSection(self, directive):
|
647 |
|
"""Reset section checking for preprocessor directive.
|
648 |
|
|
649 |
|
Args:
|
650 |
|
directive: preprocessor directive (e.g. "if", "else").
|
651 |
|
"""
|
652 |
|
# The name of the current section.
|
653 |
|
self._section = self._INITIAL_SECTION
|
654 |
|
# The path of last found header.
|
655 |
|
self._last_header = ''
|
656 |
|
|
657 |
|
# Update list of includes. Note that we never pop from the
|
658 |
|
# include list.
|
659 |
|
if directive in ('if', 'ifdef', 'ifndef'):
|
660 |
|
self.include_list.append([])
|
661 |
|
elif directive in ('else', 'elif'):
|
662 |
|
self.include_list[-1] = []
|
663 |
|
|
664 |
|
def SetLastHeader(self, header_path):
|
665 |
|
self._last_header = header_path
|
666 |
|
|
667 |
|
def CanonicalizeAlphabeticalOrder(self, header_path):
|
668 |
|
"""Returns a path canonicalized for alphabetical comparison.
|
669 |
|
|
670 |
|
- replaces "-" with "_" so they both cmp the same.
|
671 |
|
- removes '-inl' since we don't require them to be after the main header.
|
672 |
|
- lowercase everything, just in case.
|
673 |
|
|
674 |
|
Args:
|
675 |
|
header_path: Path to be canonicalized.
|
676 |
|
|
677 |
|
Returns:
|
678 |
|
Canonicalized path.
|
679 |
|
"""
|
680 |
|
return header_path.replace('-inl.h', '.h').replace('-', '_').lower()
|
681 |
|
|
682 |
|
def IsInAlphabeticalOrder(self, clean_lines, linenum, header_path):
|
683 |
|
"""Check if a header is in alphabetical order with the previous header.
|
684 |
|
|
685 |
|
Args:
|
686 |
|
clean_lines: A CleansedLines instance containing the file.
|
687 |
|
linenum: The number of the line to check.
|
688 |
|
header_path: Canonicalized header to be checked.
|
689 |
|
|
690 |
|
Returns:
|
691 |
|
Returns true if the header is in alphabetical order.
|
692 |
|
"""
|
693 |
|
# If previous section is different from current section, _last_header will
|
694 |
|
# be reset to empty string, so it's always less than current header.
|
695 |
|
#
|
696 |
|
# If previous line was a blank line, assume that the headers are
|
697 |
|
# intentionally sorted the way they are.
|
698 |
|
if (self._last_header > header_path and
|
699 |
|
Match(r'^\s*#\s*include\b', clean_lines.elided[linenum - 1])):
|
700 |
|
return False
|
701 |
|
return True
|
702 |
|
|
703 |
|
def CheckNextIncludeOrder(self, header_type):
|
704 |
|
"""Returns a non-empty error message if the next header is out of order.
|
705 |
|
|
706 |
|
This function also updates the internal state to be ready to check
|
707 |
|
the next include.
|
708 |
|
|
709 |
|
Args:
|
710 |
|
header_type: One of the _XXX_HEADER constants defined above.
|
711 |
|
|
712 |
|
Returns:
|
713 |
|
The empty string if the header is in the right order, or an
|
714 |
|
error message describing what's wrong.
|
715 |
|
|
716 |
|
"""
|
717 |
|
error_message = ('Found %s after %s' %
|
718 |
|
(self._TYPE_NAMES[header_type],
|
719 |
|
self._SECTION_NAMES[self._section]))
|
720 |
|
|
721 |
|
last_section = self._section
|
722 |
|
|
723 |
|
if header_type == _C_SYS_HEADER:
|
724 |
|
if self._section <= self._C_SECTION:
|
725 |
|
self._section = self._C_SECTION
|
726 |
|
else:
|
727 |
|
self._last_header = ''
|
728 |
|
return error_message
|
729 |
|
elif header_type == _CPP_SYS_HEADER:
|
730 |
|
if self._section <= self._CPP_SECTION:
|
731 |
|
self._section = self._CPP_SECTION
|
732 |
|
else:
|
733 |
|
self._last_header = ''
|
734 |
|
return error_message
|
735 |
|
elif header_type == _LIKELY_MY_HEADER:
|
736 |
|
if self._section <= self._MY_H_SECTION:
|
737 |
|
self._section = self._MY_H_SECTION
|
738 |
|
else:
|
739 |
|
self._section = self._OTHER_H_SECTION
|
740 |
|
elif header_type == _POSSIBLE_MY_HEADER:
|
741 |
|
if self._section <= self._MY_H_SECTION:
|
742 |
|
self._section = self._MY_H_SECTION
|
743 |
|
else:
|
744 |
|
# This will always be the fallback because we're not sure
|
745 |
|
# enough that the header is associated with this file.
|
746 |
|
self._section = self._OTHER_H_SECTION
|
747 |
|
else:
|
748 |
|
assert header_type == _OTHER_HEADER
|
749 |
|
self._section = self._OTHER_H_SECTION
|
750 |
|
|
751 |
|
if last_section != self._section:
|
752 |
|
self._last_header = ''
|
753 |
|
|
754 |
|
return ''
|
755 |
|
|
756 |
|
|
757 |
|
class _CppLintState(object):
|
758 |
|
"""Maintains module-wide state.."""
|
759 |
|
|
760 |
|
def __init__(self):
|
761 |
|
self.verbose_level = 1 # global setting.
|
762 |
|
self.error_count = 0 # global count of reported errors
|
763 |
|
# filters to apply when emitting error messages
|
764 |
|
self.filters = _DEFAULT_FILTERS[:]
|
765 |
|
# backup of filter list. Used to restore the state after each file.
|
766 |
|
self._filters_backup = self.filters[:]
|
767 |
|
self.counting = 'total' # In what way are we counting errors?
|
768 |
|
self.errors_by_category = {} # string to int dict storing error counts
|
769 |
|
|
770 |
|
# output format:
|
771 |
|
# "emacs" - format that emacs can parse (default)
|
772 |
|
# "vs7" - format that Microsoft Visual Studio 7 can parse
|
773 |
|
self.output_format = 'emacs'
|
774 |
|
|
775 |
|
def SetOutputFormat(self, output_format):
|
776 |
|
"""Sets the output format for errors."""
|
777 |
|
self.output_format = output_format
|
778 |
|
|
779 |
|
def SetVerboseLevel(self, level):
|
780 |
|
"""Sets the module's verbosity, and returns the previous setting."""
|
781 |
|
last_verbose_level = self.verbose_level
|
782 |
|
self.verbose_level = level
|
783 |
|
return last_verbose_level
|
784 |
|
|
785 |
|
def SetCountingStyle(self, counting_style):
|
786 |
|
"""Sets the module's counting options."""
|
787 |
|
self.counting = counting_style
|
788 |
|
|
789 |
|
def SetFilters(self, filters):
|
790 |
|
"""Sets the error-message filters.
|
791 |
|
|
792 |
|
These filters are applied when deciding whether to emit a given
|
793 |
|
error message.
|
794 |
|
|
795 |
|
Args:
|
796 |
|
filters: A string of comma-separated filters (eg "+whitespace/indent").
|
797 |
|
Each filter should start with + or -; else we die.
|
798 |
|
|
799 |
|
Raises:
|
800 |
|
ValueError: The comma-separated filters did not all start with '+' or '-'.
|
801 |
|
E.g. "-,+whitespace,-whitespace/indent,whitespace/badfilter"
|
802 |
|
"""
|
803 |
|
# Default filters always have less priority than the flag ones.
|
804 |
|
self.filters = _DEFAULT_FILTERS[:]
|
805 |
|
self.AddFilters(filters)
|
806 |
|
|
807 |
|
def AddFilters(self, filters):
|
808 |
|
""" Adds more filters to the existing list of error-message filters. """
|
809 |
|
for filt in filters.split(','):
|
810 |
|
clean_filt = filt.strip()
|
811 |
|
if clean_filt:
|
812 |
|
self.filters.append(clean_filt)
|
813 |
|
for filt in self.filters:
|
814 |
|
if not (filt.startswith('+') or filt.startswith('-')):
|
815 |
|
raise ValueError('Every filter in --filters must start with + or -'
|
816 |
|
' (%s does not)' % filt)
|
817 |
|
|
818 |
|
def BackupFilters(self):
|
819 |
|
""" Saves the current filter list to backup storage."""
|
820 |
|
self._filters_backup = self.filters[:]
|
821 |
|
|
822 |
|
def RestoreFilters(self):
|
823 |
|
""" Restores filters previously backed up."""
|
824 |
|
self.filters = self._filters_backup[:]
|
825 |
|
|
826 |
|
def ResetErrorCounts(self):
|
827 |
|
"""Sets the module's error statistic back to zero."""
|
828 |
|
self.error_count = 0
|
829 |
|
self.errors_by_category = {}
|
830 |
|
|
831 |
|
def IncrementErrorCount(self, category):
|
832 |
|
"""Bumps the module's error statistic."""
|
833 |
|
self.error_count += 1
|
834 |
|
if self.counting in ('toplevel', 'detailed'):
|
835 |
|
if self.counting != 'detailed':
|
836 |
|
category = category.split('/')[0]
|
837 |
|
if category not in self.errors_by_category:
|
838 |
|
self.errors_by_category[category] = 0
|
839 |
|
self.errors_by_category[category] += 1
|
840 |
|
|
841 |
|
def PrintErrorCounts(self):
|
842 |
|
"""Print a summary of errors by category, and the total."""
|
843 |
|
for category, count in self.errors_by_category.iteritems():
|
844 |
|
sys.stderr.write('Category \'%s\' errors found: %d\n' %
|
845 |
|
(category, count))
|
846 |
|
sys.stderr.write('Total errors found: %d\n' % self.error_count)
|
847 |
|
|
848 |
|
_cpplint_state = _CppLintState()
|
849 |
|
|
850 |
|
|
851 |
|
def _OutputFormat():
|
852 |
|
"""Gets the module's output format."""
|
853 |
|
return _cpplint_state.output_format
|
854 |
|
|
855 |
|
|
856 |
|
def _SetOutputFormat(output_format):
|
857 |
|
"""Sets the module's output format."""
|
858 |
|
_cpplint_state.SetOutputFormat(output_format)
|
859 |
|
|
860 |
|
|
861 |
|
def _VerboseLevel():
|
862 |
|
"""Returns the module's verbosity setting."""
|
863 |
|
return _cpplint_state.verbose_level
|
864 |
|
|
865 |
|
|
866 |
|
def _SetVerboseLevel(level):
|
867 |
|
"""Sets the module's verbosity, and returns the previous setting."""
|
868 |
|
return _cpplint_state.SetVerboseLevel(level)
|
869 |
|
|
870 |
|
|
871 |
|
def _SetCountingStyle(level):
|
872 |
|
"""Sets the module's counting options."""
|
873 |
|
_cpplint_state.SetCountingStyle(level)
|
874 |
|
|
875 |
|
|
876 |
|
def _Filters():
|
877 |
|
"""Returns the module's list of output filters, as a list."""
|
878 |
|
return _cpplint_state.filters
|
879 |
|
|
880 |
|
|
881 |
|
def _SetFilters(filters):
|
882 |
|
"""Sets the module's error-message filters.
|
883 |
|
|
884 |
|
These filters are applied when deciding whether to emit a given
|
885 |
|
error message.
|
886 |
|
|
887 |
|
Args:
|
888 |
|
filters: A string of comma-separated filters (eg "whitespace/indent").
|
889 |
|
Each filter should start with + or -; else we die.
|
890 |
|
"""
|
891 |
|
_cpplint_state.SetFilters(filters)
|
892 |
|
|
893 |
|
def _AddFilters(filters):
|
894 |
|
"""Adds more filter overrides.
|
895 |
|
|
896 |
|
Unlike _SetFilters, this function does not reset the current list of filters
|
897 |
|
available.
|
898 |
|
|
899 |
|
Args:
|
900 |
|
filters: A string of comma-separated filters (eg "whitespace/indent").
|
901 |
|
Each filter should start with + or -; else we die.
|
902 |
|
"""
|
903 |
|
_cpplint_state.AddFilters(filters)
|
904 |
|
|
905 |
|
def _BackupFilters():
|
906 |
|
""" Saves the current filter list to backup storage."""
|
907 |
|
_cpplint_state.BackupFilters()
|
908 |
|
|
909 |
|
def _RestoreFilters():
|
910 |
|
""" Restores filters previously backed up."""
|
911 |
|
_cpplint_state.RestoreFilters()
|
912 |
|
|
913 |
|
class _FunctionState(object):
|
914 |
|
"""Tracks current function name and the number of lines in its body."""
|
915 |
|
|
916 |
|
_NORMAL_TRIGGER = 250 # for --v=0, 500 for --v=1, etc.
|
917 |
|
_TEST_TRIGGER = 400 # about 50% more than _NORMAL_TRIGGER.
|
918 |
|
|
919 |
|
def __init__(self):
|
920 |
|
self.in_a_function = False
|
921 |
|
self.lines_in_function = 0
|
922 |
|
self.current_function = ''
|
923 |
|
|
924 |
|
def Begin(self, function_name):
|
925 |
|
"""Start analyzing function body.
|
926 |
|
|
927 |
|
Args:
|
928 |
|
function_name: The name of the function being tracked.
|
929 |
|
"""
|
930 |
|
self.in_a_function = True
|
931 |
|
self.lines_in_function = 0
|
932 |
|
self.current_function = function_name
|
933 |
|
|
934 |
|
def Count(self):
|
935 |
|
"""Count line in current function body."""
|
936 |
|
if self.in_a_function:
|
937 |
|
self.lines_in_function += 1
|
938 |
|
|
939 |
|
def Check(self, error, filename, linenum):
|
940 |
|
"""Report if too many lines in function body.
|
941 |
|
|
942 |
|
Args:
|
943 |
|
error: The function to call with any errors found.
|
944 |
|
filename: The name of the current file.
|
945 |
|
linenum: The number of the line to check.
|
946 |
|
"""
|
947 |
|
if Match(r'T(EST|est)', self.current_function):
|
948 |
|
base_trigger = self._TEST_TRIGGER
|
949 |
|
else:
|
950 |
|
base_trigger = self._NORMAL_TRIGGER
|
951 |
|
trigger = base_trigger * 2**_VerboseLevel()
|
952 |
|
|
953 |
|
if self.lines_in_function > trigger:
|
954 |
|
error_level = int(math.log(self.lines_in_function / base_trigger, 2))
|
955 |
|
# 50 => 0, 100 => 1, 200 => 2, 400 => 3, 800 => 4, 1600 => 5, ...
|
956 |
|
if error_level > 5:
|
957 |
|
error_level = 5
|
958 |
|
error(filename, linenum, 'readability/fn_size', error_level,
|
959 |
|
'Small and focused functions are preferred:'
|
960 |
|
' %s has %d non-comment lines'
|
961 |
|
' (error triggered by exceeding %d lines).' % (
|
962 |
|
self.current_function, self.lines_in_function, trigger))
|
963 |
|
|
964 |
|
def End(self):
|
965 |
|
"""Stop analyzing function body."""
|
966 |
|
self.in_a_function = False
|
967 |
|
|
968 |
|
|
969 |
|
class _IncludeError(Exception):
|
970 |
|
"""Indicates a problem with the include order in a file."""
|
971 |
|
pass
|
972 |
|
|
973 |
|
|
974 |
|
class FileInfo(object):
|
975 |
|
"""Provides utility functions for filenames.
|
976 |
|
|
977 |
|
FileInfo provides easy access to the components of a file's path
|
978 |
|
relative to the project root.
|
979 |
|
"""
|
980 |
|
|
981 |
|
def __init__(self, filename):
|
982 |
|
self._filename = filename
|
983 |
|
|
984 |
|
def FullName(self):
|
985 |
|
"""Make Windows paths like Unix."""
|
986 |
|
return os.path.abspath(self._filename).replace('\\', '/')
|
987 |
|
|
988 |
|
def RepositoryName(self):
|
989 |
|
"""FullName after removing the local path to the repository.
|
990 |
|
|
991 |
|
If we have a real absolute path name here we can try to do something smart:
|
992 |
|
detecting the root of the checkout and truncating /path/to/checkout from
|
993 |
|
the name so that we get header guards that don't include things like
|
994 |
|
"C:\Documents and Settings\..." or "/home/username/..." in them and thus
|
995 |
|
people on different computers who have checked the source out to different
|
996 |
|
locations won't see bogus errors.
|
997 |
|
"""
|
998 |
|
fullname = self.FullName()
|
999 |
|
|
1000 |
|
if os.path.exists(fullname):
|
1001 |
|
project_dir = os.path.dirname(fullname)
|
1002 |
|
|
1003 |
|
if os.path.exists(os.path.join(project_dir, ".svn")):
|
1004 |
|
# If there's a .svn file in the current directory, we recursively look
|
1005 |
|
# up the directory tree for the top of the SVN checkout
|
1006 |
|
root_dir = project_dir
|
1007 |
|
one_up_dir = os.path.dirname(root_dir)
|
1008 |
|
while os.path.exists(os.path.join(one_up_dir, ".svn")):
|
1009 |
|
root_dir = os.path.dirname(root_dir)
|
1010 |
|
one_up_dir = os.path.dirname(one_up_dir)
|
1011 |
|
|
1012 |
|
prefix = os.path.commonprefix([root_dir, project_dir])
|
1013 |
|
return fullname[len(prefix) + 1:]
|
1014 |
|
|
1015 |
|
# Not SVN <= 1.6? Try to find a git, hg, or svn top level directory by
|
1016 |
|
# searching up from the current path.
|
1017 |
|
root_dir = os.path.dirname(fullname)
|
1018 |
|
while (root_dir != os.path.dirname(root_dir) and
|
1019 |
|
not os.path.exists(os.path.join(root_dir, ".git")) and
|
1020 |
|
not os.path.exists(os.path.join(root_dir, ".hg")) and
|
1021 |
|
not os.path.exists(os.path.join(root_dir, ".svn"))):
|
1022 |
|
root_dir = os.path.dirname(root_dir)
|
1023 |
|
|
1024 |
|
if (os.path.exists(os.path.join(root_dir, ".git")) or
|
1025 |
|
os.path.exists(os.path.join(root_dir, ".hg")) or
|
1026 |
|
os.path.exists(os.path.join(root_dir, ".svn"))):
|
1027 |
|
prefix = os.path.commonprefix([root_dir, project_dir])
|
1028 |
|
return fullname[len(prefix) + 1:]
|
1029 |
|
|
1030 |
|
# Don't know what to do; header guard warnings may be wrong...
|
1031 |
|
return fullname
|
1032 |
|
|
1033 |
|
def Split(self):
|
1034 |
|
"""Splits the file into the directory, basename, and extension.
|
1035 |
|
|
1036 |
|
For 'chrome/browser/browser.cc', Split() would
|
1037 |
|
return ('chrome/browser', 'browser', '.cc')
|
1038 |
|
|
1039 |
|
Returns:
|
1040 |
|
A tuple of (directory, basename, extension).
|
1041 |
|
"""
|
1042 |
|
|
1043 |
|
googlename = self.RepositoryName()
|
1044 |
|
project, rest = os.path.split(googlename)
|
1045 |
|
return (project,) + os.path.splitext(rest)
|
1046 |
|
|
1047 |
|
def BaseName(self):
|
1048 |
|
"""File base name - text after the final slash, before the final period."""
|
1049 |
|
return self.Split()[1]
|
1050 |
|
|
1051 |
|
def Extension(self):
|
1052 |
|
"""File extension - text following the final period."""
|
1053 |
|
return self.Split()[2]
|
1054 |
|
|
1055 |
|
def NoExtension(self):
|
1056 |
|
"""File has no source file extension."""
|
1057 |
|
return '/'.join(self.Split()[0:2])
|
1058 |
|
|
1059 |
|
def IsSource(self):
|
1060 |
|
"""File has a source file extension."""
|
1061 |
|
return self.Extension()[1:] in ('c', 'cc', 'cpp', 'cxx')
|
1062 |
|
|
1063 |
|
|
1064 |
|
def _ShouldPrintError(category, confidence, linenum):
|
1065 |
|
"""If confidence >= verbose, category passes filter and is not suppressed."""
|
1066 |
|
|
1067 |
|
# There are three ways we might decide not to print an error message:
|
1068 |
|
# a "NOLINT(category)" comment appears in the source,
|
1069 |
|
# the verbosity level isn't high enough, or the filters filter it out.
|
1070 |
|
if IsErrorSuppressedByNolint(category, linenum):
|
1071 |
|
return False
|
1072 |
|
|
1073 |
|
if confidence < _cpplint_state.verbose_level:
|
1074 |
|
return False
|
1075 |
|
|
1076 |
|
is_filtered = False
|
1077 |
|
for one_filter in _Filters():
|
1078 |
|
if one_filter.startswith('-'):
|
1079 |
|
if category.startswith(one_filter[1:]):
|
1080 |
|
is_filtered = True
|
1081 |
|
elif one_filter.startswith('+'):
|
1082 |
|
if category.startswith(one_filter[1:]):
|
1083 |
|
is_filtered = False
|
1084 |
|
else:
|
1085 |
|
assert False # should have been checked for in SetFilter.
|
1086 |
|
if is_filtered:
|
1087 |
|
return False
|
1088 |
|
|
1089 |
|
return True
|
1090 |
|
|
1091 |
|
|
1092 |
|
def Error(filename, linenum, category, confidence, message):
|
1093 |
|
"""Logs the fact we've found a lint error.
|
1094 |
|
|
1095 |
|
We log where the error was found, and also our confidence in the error,
|
1096 |
|
that is, how certain we are this is a legitimate style regression, and
|
1097 |
|
not a misidentification or a use that's sometimes justified.
|
1098 |
|
|
1099 |
|
False positives can be suppressed by the use of
|
1100 |
|
"cpplint(category)" comments on the offending line. These are
|
1101 |
|
parsed into _error_suppressions.
|
1102 |
|
|
1103 |
|
Args:
|
1104 |
|
filename: The name of the file containing the error.
|
1105 |
|
linenum: The number of the line containing the error.
|
1106 |
|
category: A string used to describe the "category" this bug
|
1107 |
|
falls under: "whitespace", say, or "runtime". Categories
|
1108 |
|
may have a hierarchy separated by slashes: "whitespace/indent".
|
1109 |
|
confidence: A number from 1-5 representing a confidence score for
|
1110 |
|
the error, with 5 meaning that we are certain of the problem,
|
1111 |
|
and 1 meaning that it could be a legitimate construct.
|
1112 |
|
message: The error message.
|
1113 |
|
"""
|
1114 |
|
if _ShouldPrintError(category, confidence, linenum):
|
1115 |
|
_cpplint_state.IncrementErrorCount(category)
|
1116 |
|
if _cpplint_state.output_format == 'vs7':
|
1117 |
|
sys.stderr.write('%s(%s): %s [%s] [%d]\n' % (
|
1118 |
|
filename, linenum, message, category, confidence))
|
1119 |
|
elif _cpplint_state.output_format == 'eclipse':
|
1120 |
|
sys.stderr.write('%s:%s: warning: %s [%s] [%d]\n' % (
|
1121 |
|
filename, linenum, message, category, confidence))
|
1122 |
|
else:
|
1123 |
|
sys.stderr.write('%s:%s: %s [%s] [%d]\n' % (
|
1124 |
|
filename, linenum, message, category, confidence))
|
1125 |
|
|
1126 |
|
|
1127 |
|
# Matches standard C++ escape sequences per 2.13.2.3 of the C++ standard.
|
1128 |
|
_RE_PATTERN_CLEANSE_LINE_ESCAPES = re.compile(
|
1129 |
|
r'\\([abfnrtv?"\\\']|\d+|x[0-9a-fA-F]+)')
|
1130 |
|
# Match a single C style comment on the same line.
|
1131 |
|
_RE_PATTERN_C_COMMENTS = r'/\*(?:[^*]|\*(?!/))*\*/'
|
1132 |
|
# Matches multi-line C style comments.
|
1133 |
|
# This RE is a little bit more complicated than one might expect, because we
|
1134 |
|
# have to take care of space removals tools so we can handle comments inside
|
1135 |
|
# statements better.
|
1136 |
|
# The current rule is: We only clear spaces from both sides when we're at the
|
1137 |
|
# end of the line. Otherwise, we try to remove spaces from the right side,
|
1138 |
|
# if this doesn't work we try on left side but only if there's a non-character
|
1139 |
|
# on the right.
|
1140 |
|
_RE_PATTERN_CLEANSE_LINE_C_COMMENTS = re.compile(
|
1141 |
|
r'(\s*' + _RE_PATTERN_C_COMMENTS + r'\s*$|' +
|
1142 |
|
_RE_PATTERN_C_COMMENTS + r'\s+|' +
|
1143 |
|
r'\s+' + _RE_PATTERN_C_COMMENTS + r'(?=\W)|' +
|
1144 |
|
_RE_PATTERN_C_COMMENTS + r')')
|
1145 |
|
|
1146 |
|
|
1147 |
|
def IsCppString(line):
|
1148 |
|
"""Does line terminate so, that the next symbol is in string constant.
|
1149 |
|
|
1150 |
|
This function does not consider single-line nor multi-line comments.
|
1151 |
|
|
1152 |
|
Args:
|
1153 |
|
line: is a partial line of code starting from the 0..n.
|
1154 |
|
|
1155 |
|
Returns:
|
1156 |
|
True, if next character appended to 'line' is inside a
|
1157 |
|
string constant.
|
1158 |
|
"""
|
1159 |
|
|
1160 |
|
line = line.replace(r'\\', 'XX') # after this, \\" does not match to \"
|
1161 |
|
return ((line.count('"') - line.count(r'\"') - line.count("'\"'")) & 1) == 1
|
1162 |
|
|
1163 |
|
|
1164 |
|
def CleanseRawStrings(raw_lines):
|
1165 |
|
"""Removes C++11 raw strings from lines.
|
1166 |
|
|
1167 |
|
Before:
|
1168 |
|
static const char kData[] = R"(
|
1169 |
|
multi-line string
|
1170 |
|
)";
|
1171 |
|
|
1172 |
|
After:
|
1173 |
|
static const char kData[] = ""
|
1174 |
|
(replaced by blank line)
|
1175 |
|
"";
|
1176 |
|
|
1177 |
|
Args:
|
1178 |
|
raw_lines: list of raw lines.
|
1179 |
|
|
1180 |
|
Returns:
|
1181 |
|
list of lines with C++11 raw strings replaced by empty strings.
|
1182 |
|
"""
|
1183 |
|
|
1184 |
|
delimiter = None
|
1185 |
|
lines_without_raw_strings = []
|
1186 |
|
for line in raw_lines:
|
1187 |
|
if delimiter:
|
1188 |
|
# Inside a raw string, look for the end
|
1189 |
|
end = line.find(delimiter)
|
1190 |
|
if end >= 0:
|
1191 |
|
# Found the end of the string, match leading space for this
|
1192 |
|
# line and resume copying the original lines, and also insert
|
1193 |
|
# a "" on the last line.
|
1194 |
|
leading_space = Match(r'^(\s*)\S', line)
|
1195 |
|
line = leading_space.group(1) + '""' + line[end + len(delimiter):]
|
1196 |
|
delimiter = None
|
1197 |
|
else:
|
1198 |
|
# Haven't found the end yet, append a blank line.
|
1199 |
|
line = '""'
|
1200 |
|
|
1201 |
|
# Look for beginning of a raw string, and replace them with
|
1202 |
|
# empty strings. This is done in a loop to handle multiple raw
|
1203 |
|
# strings on the same line.
|
1204 |
|
while delimiter is None:
|
1205 |
|
# Look for beginning of a raw string.
|
1206 |
|
# See 2.14.15 [lex.string] for syntax.
|
1207 |
|
matched = Match(r'^(.*)\b(?:R|u8R|uR|UR|LR)"([^\s\\()]*)\((.*)$', line)
|
1208 |
|
if matched:
|
1209 |
|
delimiter = ')' + matched.group(2) + '"'
|
1210 |
|
|
1211 |
|
end = matched.group(3).find(delimiter)
|
1212 |
|
if end >= 0:
|
1213 |
|
# Raw string ended on same line
|
1214 |
|
line = (matched.group(1) + '""' +
|
1215 |
|
matched.group(3)[end + len(delimiter):])
|
1216 |
|
delimiter = None
|
1217 |
|
else:
|
1218 |
|
# Start of a multi-line raw string
|
1219 |
|
line = matched.group(1) + '""'
|
1220 |
|
else:
|
1221 |
|
break
|
1222 |
|
|
1223 |
|
lines_without_raw_strings.append(line)
|
1224 |
|
|
1225 |
|
# TODO(unknown): if delimiter is not None here, we might want to
|
1226 |
|
# emit a warning for unterminated string.
|
1227 |
|
return lines_without_raw_strings
|
1228 |
|
|
1229 |
|
|
1230 |
|
def FindNextMultiLineCommentStart(lines, lineix):
|
1231 |
|
"""Find the beginning marker for a multiline comment."""
|
1232 |
|
while lineix < len(lines):
|
1233 |
|
if lines[lineix].strip().startswith('/*'):
|
1234 |
|
# Only return this marker if the comment goes beyond this line
|
1235 |
|
if lines[lineix].strip().find('*/', 2) < 0:
|
1236 |
|
return lineix
|
1237 |
|
lineix += 1
|
1238 |
|
return len(lines)
|
1239 |
|
|
1240 |
|
|
1241 |
|
def FindNextMultiLineCommentEnd(lines, lineix):
|
1242 |
|
"""We are inside a comment, find the end marker."""
|
1243 |
|
while lineix < len(lines):
|
1244 |
|
if lines[lineix].strip().endswith('*/'):
|
1245 |
|
return lineix
|
1246 |
|
lineix += 1
|
1247 |
|
return len(lines)
|
1248 |
|
|
1249 |
|
|
1250 |
|
def RemoveMultiLineCommentsFromRange(lines, begin, end):
|
1251 |
|
"""Clears a range of lines for multi-line comments."""
|
1252 |
|
# Having // dummy comments makes the lines non-empty, so we will not get
|
1253 |
|
# unnecessary blank line warnings later in the code.
|
1254 |
|
for i in range(begin, end):
|
1255 |
|
lines[i] = '/**/'
|
1256 |
|
|
1257 |
|
|
1258 |
|
def RemoveMultiLineComments(filename, lines, error):
|
1259 |
|
"""Removes multiline (c-style) comments from lines."""
|
1260 |
|
lineix = 0
|
1261 |
|
while lineix < len(lines):
|
1262 |
|
lineix_begin = FindNextMultiLineCommentStart(lines, lineix)
|
1263 |
|
if lineix_begin >= len(lines):
|
1264 |
|
return
|
1265 |
|
lineix_end = FindNextMultiLineCommentEnd(lines, lineix_begin)
|
1266 |
|
if lineix_end >= len(lines):
|
1267 |
|
error(filename, lineix_begin + 1, 'readability/multiline_comment', 5,
|
1268 |
|
'Could not find end of multi-line comment')
|
1269 |
|
return
|
1270 |
|
RemoveMultiLineCommentsFromRange(lines, lineix_begin, lineix_end + 1)
|
1271 |
|
lineix = lineix_end + 1
|
1272 |
|
|
1273 |
|
|
1274 |
|
def CleanseComments(line):
|
1275 |
|
"""Removes //-comments and single-line C-style /* */ comments.
|
1276 |
|
|
1277 |
|
Args:
|
1278 |
|
line: A line of C++ source.
|
1279 |
|
|
1280 |
|
Returns:
|
1281 |
|
The line with single-line comments removed.
|
1282 |
|
"""
|
1283 |
|
commentpos = line.find('//')
|
1284 |
|
if commentpos != -1 and not IsCppString(line[:commentpos]):
|
1285 |
|
line = line[:commentpos].rstrip()
|
1286 |
|
# get rid of /* ... */
|
1287 |
|
return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub('', line)
|
1288 |
|
|
1289 |
|
|
1290 |
|
class CleansedLines(object):
|
1291 |
|
"""Holds 4 copies of all lines with different preprocessing applied to them.
|
1292 |
|
|
1293 |
|
1) elided member contains lines without strings and comments.
|
1294 |
|
2) lines member contains lines without comments.
|
1295 |
|
3) raw_lines member contains all the lines without processing.
|
1296 |
|
4) lines_without_raw_strings member is same as raw_lines, but with C++11 raw
|
1297 |
|
strings removed.
|
1298 |
|
All these members are of <type 'list'>, and of the same length.
|
1299 |
|
"""
|
1300 |
|
|
1301 |
|
def __init__(self, lines):
|
1302 |
|
self.elided = []
|
1303 |
|
self.lines = []
|
1304 |
|
self.raw_lines = lines
|
1305 |
|
self.num_lines = len(lines)
|
1306 |
|
self.lines_without_raw_strings = CleanseRawStrings(lines)
|
1307 |
|
for linenum in range(len(self.lines_without_raw_strings)):
|
1308 |
|
self.lines.append(CleanseComments(
|
1309 |
|
self.lines_without_raw_strings[linenum]))
|
1310 |
|
elided = self._CollapseStrings(self.lines_without_raw_strings[linenum])
|
1311 |
|
self.elided.append(CleanseComments(elided))
|
1312 |
|
|
1313 |
|
def NumLines(self):
|
1314 |
|
"""Returns the number of lines represented."""
|
1315 |
|
return self.num_lines
|
1316 |
|
|
1317 |
|
@staticmethod
|
1318 |
|
def _CollapseStrings(elided):
|
1319 |
|
"""Collapses strings and chars on a line to simple "" or '' blocks.
|
1320 |
|
|
1321 |
|
We nix strings first so we're not fooled by text like '"http://"'
|
1322 |
|
|
1323 |
|
Args:
|
1324 |
|
elided: The line being processed.
|
1325 |
|
|
1326 |
|
Returns:
|
1327 |
|
The line with collapsed strings.
|
1328 |
|
"""
|
1329 |
|
if _RE_PATTERN_INCLUDE.match(elided):
|
1330 |
|
return elided
|
1331 |
|
|
1332 |
|
# Remove escaped characters first to make quote/single quote collapsing
|
1333 |
|
# basic. Things that look like escaped characters shouldn't occur
|
1334 |
|
# outside of strings and chars.
|
1335 |
|
elided = _RE_PATTERN_CLEANSE_LINE_ESCAPES.sub('', elided)
|
1336 |
|
|
1337 |
|
# Replace quoted strings and digit separators. Both single quotes
|
1338 |
|
# and double quotes are processed in the same loop, otherwise
|
1339 |
|
# nested quotes wouldn't work.
|
1340 |
|
collapsed = ''
|
1341 |
|
while True:
|
1342 |
|
# Find the first quote character
|
1343 |
|
match = Match(r'^([^\'"]*)([\'"])(.*)$', elided)
|
1344 |
|
if not match:
|
1345 |
|
collapsed += elided
|
1346 |
|
break
|
1347 |
|
head, quote, tail = match.groups()
|
1348 |
|
|
1349 |
|
if quote == '"':
|
1350 |
|
# Collapse double quoted strings
|
1351 |
|
second_quote = tail.find('"')
|
1352 |
|
if second_quote >= 0:
|
1353 |
|
collapsed += head + '""'
|
1354 |
|
elided = tail[second_quote + 1:]
|
1355 |
|
else:
|
1356 |
|
# Unmatched double quote, don't bother processing the rest
|
1357 |
|
# of the line since this is probably a multiline string.
|
1358 |
|
collapsed += elided
|
1359 |
|
break
|
1360 |
|
else:
|
1361 |
|
# Found single quote, check nearby text to eliminate digit separators.
|
1362 |
|
#
|
1363 |
|
# There is no special handling for floating point here, because
|
1364 |
|
# the integer/fractional/exponent parts would all be parsed
|
1365 |
|
# correctly as long as there are digits on both sides of the
|