mirror of
https://github.com/google/styleguide.git
synced 2024-03-22 13:11:43 +08:00
Update cpplint.py to #122:
- Don't check quoted filenames with irrelevant tests. - Make cpplint accept 'for (foo; bar; ) {}'. - Work with temporary files generated by Emacs flymake-mode. - Don't warn on "/// Doxygen comments." - Check the use of DCHECK in the same way we check the use of CHECK. - Properly handle relative file paths with IncludeWhatYouUse checking. - Start checking for IncludeWhatYouUse in a limited way in .cc files.
This commit is contained in:
parent
a764d2becf
commit
e35f765fa6
226
cpplint/cpplint.py
vendored
226
cpplint/cpplint.py
vendored
|
@ -175,6 +175,12 @@ _ERROR_CATEGORIES = '''\
|
||||||
whitespace/todo
|
whitespace/todo
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
# The default state of the category filter. This is overrided by the --filter=
|
||||||
|
# flag. By default all errors are on, so only add here categories that should be
|
||||||
|
# off by default (i.e., categories that must be enabled by the --filter= flags).
|
||||||
|
# All entries here should start with a '-' or '+', as in the --filter= flag.
|
||||||
|
_DEFAULT_FILTERS = []
|
||||||
|
|
||||||
# We used to check for high-bit characters, but after much discussion we
|
# We used to check for high-bit characters, but after much discussion we
|
||||||
# decided those were OK, as long as they were in UTF-8 and didn't represent
|
# decided those were OK, as long as they were in UTF-8 and didn't represent
|
||||||
# hard-coded international strings, which belong in a seperate i18n file.
|
# hard-coded international strings, which belong in a seperate i18n file.
|
||||||
|
@ -212,19 +218,20 @@ _CPP_HEADERS = frozenset([
|
||||||
# testing/base/gunit.h. Note that the _M versions need to come first
|
# testing/base/gunit.h. Note that the _M versions need to come first
|
||||||
# for substring matching to work.
|
# for substring matching to work.
|
||||||
_CHECK_MACROS = [
|
_CHECK_MACROS = [
|
||||||
'CHECK',
|
'DCHECK', 'CHECK',
|
||||||
'EXPECT_TRUE_M', 'EXPECT_TRUE',
|
'EXPECT_TRUE_M', 'EXPECT_TRUE',
|
||||||
'ASSERT_TRUE_M', 'ASSERT_TRUE',
|
'ASSERT_TRUE_M', 'ASSERT_TRUE',
|
||||||
'EXPECT_FALSE_M', 'EXPECT_FALSE',
|
'EXPECT_FALSE_M', 'EXPECT_FALSE',
|
||||||
'ASSERT_FALSE_M', 'ASSERT_FALSE',
|
'ASSERT_FALSE_M', 'ASSERT_FALSE',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Replacement macros for CHECK/EXPECT_TRUE/EXPECT_FALSE
|
# Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE
|
||||||
_CHECK_REPLACEMENT = dict([(m, {}) for m in _CHECK_MACROS])
|
_CHECK_REPLACEMENT = dict([(m, {}) for m in _CHECK_MACROS])
|
||||||
|
|
||||||
for op, replacement in [('==', 'EQ'), ('!=', 'NE'),
|
for op, replacement in [('==', 'EQ'), ('!=', 'NE'),
|
||||||
('>=', 'GE'), ('>', 'GT'),
|
('>=', 'GE'), ('>', 'GT'),
|
||||||
('<=', 'LE'), ('<', 'LT')]:
|
('<=', 'LE'), ('<', 'LT')]:
|
||||||
|
_CHECK_REPLACEMENT['DCHECK'][op] = 'DCHECK_%s' % replacement
|
||||||
_CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement
|
_CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement
|
||||||
_CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement
|
_CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement
|
||||||
_CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement
|
_CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement
|
||||||
|
@ -360,7 +367,8 @@ class _CppLintState(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.verbose_level = 1 # global setting.
|
self.verbose_level = 1 # global setting.
|
||||||
self.error_count = 0 # global count of reported errors
|
self.error_count = 0 # global count of reported errors
|
||||||
self.filters = [] # filters to apply when emitting error messages
|
# filters to apply when emitting error messages
|
||||||
|
self.filters = _DEFAULT_FILTERS[:]
|
||||||
|
|
||||||
# output format:
|
# output format:
|
||||||
# "emacs" - format that emacs can parse (default)
|
# "emacs" - format that emacs can parse (default)
|
||||||
|
@ -391,10 +399,12 @@ class _CppLintState(object):
|
||||||
ValueError: The comma-separated filters did not all start with '+' or '-'.
|
ValueError: The comma-separated filters did not all start with '+' or '-'.
|
||||||
E.g. "-,+whitespace,-whitespace/indent,whitespace/badfilter"
|
E.g. "-,+whitespace,-whitespace/indent,whitespace/badfilter"
|
||||||
"""
|
"""
|
||||||
if not filters:
|
# Default filters always have less priority than the flag ones.
|
||||||
self.filters = []
|
self.filters = _DEFAULT_FILTERS[:]
|
||||||
else:
|
for filt in filters.split(','):
|
||||||
self.filters = filters.split(',')
|
clean_filt = filt.strip()
|
||||||
|
if clean_filt:
|
||||||
|
self.filters.append(clean_filt)
|
||||||
for filt in self.filters:
|
for filt in self.filters:
|
||||||
if not (filt.startswith('+') or filt.startswith('-')):
|
if not (filt.startswith('+') or filt.startswith('-')):
|
||||||
raise ValueError('Every filter in --filters must start with + or -'
|
raise ValueError('Every filter in --filters must start with + or -'
|
||||||
|
@ -1546,7 +1556,10 @@ def CheckSpacing(filename, clean_lines, linenum, error):
|
||||||
# but some lines are exceptions -- e.g. if they're big
|
# but some lines are exceptions -- e.g. if they're big
|
||||||
# comment delimiters like:
|
# comment delimiters like:
|
||||||
# //----------------------------------------------------------
|
# //----------------------------------------------------------
|
||||||
match = Search(r'[=/-]{4,}\s*$', line[commentend:])
|
# or they begin with multiple slashes followed by a space:
|
||||||
|
# //////// Header comment
|
||||||
|
match = (Search(r'[=/-]{4,}\s*$', line[commentend:]) or
|
||||||
|
Search(r'^/+ ', line[commentend:]))
|
||||||
if not match:
|
if not match:
|
||||||
error(filename, linenum, 'whitespace/comments', 4,
|
error(filename, linenum, 'whitespace/comments', 4,
|
||||||
'Should have a space between // and comment')
|
'Should have a space between // and comment')
|
||||||
|
@ -1607,14 +1620,15 @@ def CheckSpacing(filename, clean_lines, linenum, error):
|
||||||
# consistent about how many spaces are inside the parens, and
|
# consistent about how many spaces are inside the parens, and
|
||||||
# there should either be zero or one spaces inside the parens.
|
# there should either be zero or one spaces inside the parens.
|
||||||
# We don't want: "if ( foo)" or "if ( foo )".
|
# We don't want: "if ( foo)" or "if ( foo )".
|
||||||
# Exception: "for ( ; foo; bar)" is allowed.
|
# Exception: "for ( ; foo; bar)" and "for (foo; bar; )" are allowed.
|
||||||
match = Search(r'\b(if|for|while|switch)\s*'
|
match = Search(r'\b(if|for|while|switch)\s*'
|
||||||
r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$',
|
r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$',
|
||||||
line)
|
line)
|
||||||
if match:
|
if match:
|
||||||
if len(match.group(2)) != len(match.group(4)):
|
if len(match.group(2)) != len(match.group(4)):
|
||||||
if not (match.group(3) == ';' and
|
if not (match.group(3) == ';' and
|
||||||
len(match.group(2)) == 1 + len(match.group(4))):
|
len(match.group(2)) == 1 + len(match.group(4)) or
|
||||||
|
not match.group(2) and Search(r'\bfor\s*\(.*; \)', line)):
|
||||||
error(filename, linenum, 'whitespace/parens', 5,
|
error(filename, linenum, 'whitespace/parens', 5,
|
||||||
'Mismatching spaces inside () in %s' % match.group(1))
|
'Mismatching spaces inside () in %s' % match.group(1))
|
||||||
if not len(match.group(2)) in [0, 1]:
|
if not len(match.group(2)) in [0, 1]:
|
||||||
|
@ -2063,35 +2077,33 @@ def _ClassifyInclude(fileinfo, include, is_system):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
|
def CheckIncludeLine(filename, clean_lines, linenum, include_state, error):
|
||||||
error):
|
"""Check rules that are applicable to #include lines.
|
||||||
"""Checks rules from the 'C++ language rules' section of cppguide.html.
|
|
||||||
|
|
||||||
Some of these rules are hard to test (function overloading, using
|
Strings on #include lines are NOT removed from elided line, to make
|
||||||
uint32 inappropriately), but we do the best we can.
|
certain tasks easier. However, to prevent false positives, checks
|
||||||
|
applicable to #include lines in CheckLanguage must be put here.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filename: The name of the current file.
|
filename: The name of the current file.
|
||||||
clean_lines: A CleansedLines instance containing the file.
|
clean_lines: A CleansedLines instance containing the file.
|
||||||
linenum: The number of the line to check.
|
linenum: The number of the line to check.
|
||||||
file_extension: The extension (without the dot) of the filename.
|
|
||||||
include_state: An _IncludeState instance in which the headers are inserted.
|
include_state: An _IncludeState instance in which the headers are inserted.
|
||||||
error: The function to call with any errors found.
|
error: The function to call with any errors found.
|
||||||
"""
|
"""
|
||||||
fileinfo = FileInfo(filename)
|
fileinfo = FileInfo(filename)
|
||||||
|
|
||||||
# get rid of comments
|
line = clean_lines.lines[linenum]
|
||||||
comment_elided_line = clean_lines.lines[linenum]
|
|
||||||
|
|
||||||
# "include" should use the new style "foo/bar.h" instead of just "bar.h"
|
# "include" should use the new style "foo/bar.h" instead of just "bar.h"
|
||||||
if _RE_PATTERN_INCLUDE_NEW_STYLE.search(comment_elided_line):
|
if _RE_PATTERN_INCLUDE_NEW_STYLE.search(line):
|
||||||
error(filename, linenum, 'build/include', 4,
|
error(filename, linenum, 'build/include', 4,
|
||||||
'Include the directory when naming .h files')
|
'Include the directory when naming .h files')
|
||||||
|
|
||||||
# we shouldn't include a file more than once. actually, there are a
|
# we shouldn't include a file more than once. actually, there are a
|
||||||
# handful of instances where doing so is okay, but in general it's
|
# handful of instances where doing so is okay, but in general it's
|
||||||
# not.
|
# not.
|
||||||
match = _RE_PATTERN_INCLUDE.search(comment_elided_line)
|
match = _RE_PATTERN_INCLUDE.search(line)
|
||||||
if match:
|
if match:
|
||||||
include = match.group(2)
|
include = match.group(2)
|
||||||
is_system = (match.group(1) == '<')
|
is_system = (match.group(1) == '<')
|
||||||
|
@ -2120,12 +2132,42 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
|
||||||
'%s. Should be: %s.h, c system, c++ system, other.' %
|
'%s. Should be: %s.h, c system, c++ system, other.' %
|
||||||
(error_message, fileinfo.BaseName()))
|
(error_message, fileinfo.BaseName()))
|
||||||
|
|
||||||
|
# Look for any of the stream classes that are part of standard C++.
|
||||||
|
match = _RE_PATTERN_INCLUDE.match(line)
|
||||||
|
if match:
|
||||||
|
include = match.group(2)
|
||||||
|
if Match(r'(f|ind|io|i|o|parse|pf|stdio|str|)?stream$', include):
|
||||||
|
# Many unit tests use cout, so we exempt them.
|
||||||
|
if not _IsTestFilename(filename):
|
||||||
|
error(filename, linenum, 'readability/streams', 3,
|
||||||
|
'Streams are highly discouraged.')
|
||||||
|
|
||||||
|
def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
|
||||||
|
error):
|
||||||
|
"""Checks rules from the 'C++ language rules' section of cppguide.html.
|
||||||
|
|
||||||
|
Some of these rules are hard to test (function overloading, using
|
||||||
|
uint32 inappropriately), but we do the best we can.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: The name of the current file.
|
||||||
|
clean_lines: A CleansedLines instance containing the file.
|
||||||
|
linenum: The number of the line to check.
|
||||||
|
file_extension: The extension (without the dot) of the filename.
|
||||||
|
include_state: An _IncludeState instance in which the headers are inserted.
|
||||||
|
error: The function to call with any errors found.
|
||||||
|
"""
|
||||||
# If the line is empty or consists of entirely a comment, no need to
|
# If the line is empty or consists of entirely a comment, no need to
|
||||||
# check it.
|
# check it.
|
||||||
line = clean_lines.elided[linenum]
|
line = clean_lines.elided[linenum]
|
||||||
if not line:
|
if not line:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
match = _RE_PATTERN_INCLUDE.search(line)
|
||||||
|
if match:
|
||||||
|
CheckIncludeLine(filename, clean_lines, linenum, include_state, error)
|
||||||
|
return
|
||||||
|
|
||||||
# Create an extended_line, which is the concatenation of the current and
|
# Create an extended_line, which is the concatenation of the current and
|
||||||
# next lines, for more effective checking of code that may span more than one
|
# next lines, for more effective checking of code that may span more than one
|
||||||
# line.
|
# line.
|
||||||
|
@ -2139,16 +2181,6 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
|
||||||
|
|
||||||
# TODO(unknown): figure out if they're using default arguments in fn proto.
|
# TODO(unknown): figure out if they're using default arguments in fn proto.
|
||||||
|
|
||||||
# Look for any of the stream classes that are part of standard C++.
|
|
||||||
match = _RE_PATTERN_INCLUDE.match(line)
|
|
||||||
if match:
|
|
||||||
include = match.group(2)
|
|
||||||
if Match(r'(f|ind|io|i|o|parse|pf|stdio|str|)?stream$', include):
|
|
||||||
# Many unit tests use cout, so we exempt them.
|
|
||||||
if not _IsTestFilename(filename):
|
|
||||||
error(filename, linenum, 'readability/streams', 3,
|
|
||||||
'Streams are highly discouraged.')
|
|
||||||
|
|
||||||
# Check for non-const references in functions. This is tricky because &
|
# Check for non-const references in functions. This is tricky because &
|
||||||
# is also used to take the address of something. We allow <> for templates,
|
# is also used to take the address of something. We allow <> for templates,
|
||||||
# (ignoring whatever is between the braces) and : for classes.
|
# (ignoring whatever is between the braces) and : for classes.
|
||||||
|
@ -2483,7 +2515,92 @@ for _header, _templates in _HEADERS_CONTAINING_TEMPLATES:
|
||||||
_header))
|
_header))
|
||||||
|
|
||||||
|
|
||||||
def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error):
|
def FilesBelongToSameModule(filename_cc, filename_h):
|
||||||
|
"""Check if these two filenames belong to the same module.
|
||||||
|
|
||||||
|
The concept of a 'module' here is a as follows:
|
||||||
|
foo.h, foo-inl.h, foo.cc, foo_test.cc and foo_unittest.cc belong to the
|
||||||
|
same 'module' if they are in the same directory.
|
||||||
|
some/path/public/xyzzy and some/path/internal/xyzzy are also considered
|
||||||
|
to belong to the same module here.
|
||||||
|
|
||||||
|
If the filename_cc contains a longer path than the filename_h, for example,
|
||||||
|
'/absolute/path/to/base/sysinfo.cc', and this file would include
|
||||||
|
'base/sysinfo.h', this function also produces the prefix needed to open the
|
||||||
|
header. This is used by the caller of this function to more robustly open the
|
||||||
|
header file. We don't have access to the real include paths in this context,
|
||||||
|
so we need this guesswork here.
|
||||||
|
|
||||||
|
Known bugs: tools/base/bar.cc and base/bar.h belong to the same module
|
||||||
|
according to this implementation. Because of this, this function gives
|
||||||
|
some false positives. This should be sufficiently rare in practice.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename_cc: is the path for the .cc file
|
||||||
|
filename_h: is the path for the header path
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple with a bool and a string:
|
||||||
|
bool: True if filename_cc and filename_h belong to the same module.
|
||||||
|
string: the additional prefix needed to open the header file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not filename_cc.endswith('.cc'):
|
||||||
|
return (False, '')
|
||||||
|
filename_cc = filename_cc[:-len('.cc')]
|
||||||
|
if filename_cc.endswith('_unittest'):
|
||||||
|
filename_cc = filename_cc[:-len('_unittest')]
|
||||||
|
elif filename_cc.endswith('_test'):
|
||||||
|
filename_cc = filename_cc[:-len('_test')]
|
||||||
|
filename_cc = filename_cc.replace('/public/', '/')
|
||||||
|
filename_cc = filename_cc.replace('/internal/', '/')
|
||||||
|
|
||||||
|
if not filename_h.endswith('.h'):
|
||||||
|
return (False, '')
|
||||||
|
filename_h = filename_h[:-len('.h')]
|
||||||
|
if filename_h.endswith('-inl'):
|
||||||
|
filename_h = filename_h[:-len('-inl')]
|
||||||
|
filename_h = filename_h.replace('/public/', '/')
|
||||||
|
filename_h = filename_h.replace('/internal/', '/')
|
||||||
|
|
||||||
|
files_belong_to_same_module = filename_cc.endswith(filename_h)
|
||||||
|
common_path = ''
|
||||||
|
if files_belong_to_same_module:
|
||||||
|
common_path = filename_cc[:-len(filename_h)]
|
||||||
|
return files_belong_to_same_module, common_path
|
||||||
|
|
||||||
|
|
||||||
|
def UpdateIncludeState(filename, include_state, io=codecs):
|
||||||
|
"""Fill up the include_state with new includes found from the file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: the name of the header to read.
|
||||||
|
include_state: an _IncludeState instance in which the headers are inserted.
|
||||||
|
io: The io factory to use to read the file. Provided for testability.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if a header was succesfully added. False otherwise.
|
||||||
|
"""
|
||||||
|
headerfile = None
|
||||||
|
try:
|
||||||
|
headerfile = io.open(filename, 'r', 'utf8', 'replace')
|
||||||
|
except IOError:
|
||||||
|
return False
|
||||||
|
linenum = 0
|
||||||
|
for line in headerfile:
|
||||||
|
linenum += 1
|
||||||
|
clean_line = CleanseComments(line)
|
||||||
|
match = _RE_PATTERN_INCLUDE.search(clean_line)
|
||||||
|
if match:
|
||||||
|
include = match.group(2)
|
||||||
|
# The value formatting is cute, but not really used right now.
|
||||||
|
# What matters here is that the key is in include_state.
|
||||||
|
include_state.setdefault(include, '%s:%d' % (filename, linenum))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error,
|
||||||
|
io=codecs):
|
||||||
"""Reports for missing stl includes.
|
"""Reports for missing stl includes.
|
||||||
|
|
||||||
This function will output warnings to make sure you are including the headers
|
This function will output warnings to make sure you are including the headers
|
||||||
|
@ -2492,19 +2609,14 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error):
|
||||||
less<> in a .h file, only one (the latter in the file) of these will be
|
less<> in a .h file, only one (the latter in the file) of these will be
|
||||||
reported as a reason to include the <functional>.
|
reported as a reason to include the <functional>.
|
||||||
|
|
||||||
We only check headers. We do not check inside cc-files. .cc files should be
|
|
||||||
able to depend on their respective header files for includes. However, there
|
|
||||||
is no simple way of producing this logic here.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filename: The name of the current file.
|
filename: The name of the current file.
|
||||||
clean_lines: A CleansedLines instance containing the file.
|
clean_lines: A CleansedLines instance containing the file.
|
||||||
include_state: An _IncludeState instance.
|
include_state: An _IncludeState instance.
|
||||||
error: The function to call with any errors found.
|
error: The function to call with any errors found.
|
||||||
|
io: The IO factory to use to read the header file. Provided for unittest
|
||||||
|
injection.
|
||||||
"""
|
"""
|
||||||
if filename.endswith('.cc'):
|
|
||||||
return
|
|
||||||
|
|
||||||
required = {} # A map of header name to linenumber and the template entity.
|
required = {} # A map of header name to linenumber and the template entity.
|
||||||
# Example of required: { '<functional>': (1219, 'less<>') }
|
# Example of required: { '<functional>': (1219, 'less<>') }
|
||||||
|
|
||||||
|
@ -2529,6 +2641,44 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error):
|
||||||
if pattern.search(line):
|
if pattern.search(line):
|
||||||
required[header] = (linenum, template)
|
required[header] = (linenum, template)
|
||||||
|
|
||||||
|
# The policy is that if you #include something in foo.h you don't need to
|
||||||
|
# include it again in foo.cc. Here, we will look at possible includes.
|
||||||
|
# Let's copy the include_state so it is only messed up within this function.
|
||||||
|
include_state = include_state.copy()
|
||||||
|
|
||||||
|
# Did we find the header for this file (if any) and succesfully load it?
|
||||||
|
header_found = False
|
||||||
|
|
||||||
|
# Use the absolute path so that matching works properly.
|
||||||
|
abs_filename = os.path.abspath(filename)
|
||||||
|
|
||||||
|
# For Emacs's flymake.
|
||||||
|
# If cpplint is invoked from Emacs's flymake, a temporary file is generated
|
||||||
|
# by flymake and that file name might end with '_flymake.cc'. In that case,
|
||||||
|
# restore original file name here so that the corresponding header file can be
|
||||||
|
# found.
|
||||||
|
# e.g. If the file name is 'foo_flymake.cc', we should search for 'foo.h'
|
||||||
|
# instead of 'foo_flymake.h'
|
||||||
|
emacs_flymake_suffix = '_flymake.cc'
|
||||||
|
if abs_filename.endswith(emacs_flymake_suffix):
|
||||||
|
abs_filename = abs_filename[:-len(emacs_flymake_suffix)] + '.cc'
|
||||||
|
|
||||||
|
# include_state is modified during iteration, so we iterate over a copy of
|
||||||
|
# the keys.
|
||||||
|
for header in include_state.keys(): #NOLINT
|
||||||
|
(same_module, common_path) = FilesBelongToSameModule(abs_filename, header)
|
||||||
|
fullpath = common_path + header
|
||||||
|
if same_module and UpdateIncludeState(fullpath, include_state, io):
|
||||||
|
header_found = True
|
||||||
|
|
||||||
|
# If we can't find the header file for a .cc, assume it's because we don't
|
||||||
|
# know where to look. In that case we'll give up as we're not sure they
|
||||||
|
# didn't include it in the .h file.
|
||||||
|
# TODO(unknown): Do a better job of finding .h files so we are confident that
|
||||||
|
# not having the .h file means there isn't one.
|
||||||
|
if filename.endswith('.cc') and not header_found:
|
||||||
|
return
|
||||||
|
|
||||||
# All the lines have been processed, report the errors found.
|
# All the lines have been processed, report the errors found.
|
||||||
for required_header_unstripped in required:
|
for required_header_unstripped in required:
|
||||||
template = required[required_header_unstripped][1]
|
template = required[required_header_unstripped][1]
|
||||||
|
|
|
@ -13,6 +13,9 @@
|
||||||
|
|
||||||
"""Unit test for cpplint.py."""
|
"""Unit test for cpplint.py."""
|
||||||
|
|
||||||
|
# TODO(unknown): Add a good test that tests UpdateIncludeState.
|
||||||
|
|
||||||
|
import codecs
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
@ -72,6 +75,18 @@ class ErrorCollector:
|
||||||
self._errors = self._errors[0:index] + self._errors[(index + 1):]
|
self._errors = self._errors[0:index] + self._errors[(index + 1):]
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
# This class is a lame mock of codecs. We do not verify filename, mode, or
|
||||||
|
# encoding, but for the current use case it is not needed.
|
||||||
|
class MockIo:
|
||||||
|
def __init__(self, mock_file):
|
||||||
|
self.mock_file = mock_file
|
||||||
|
|
||||||
|
def open(self, unused_filename, unused_mode, unused_encoding, _): # NOLINT
|
||||||
|
# (lint doesn't like open as a method name)
|
||||||
|
return self.mock_file
|
||||||
|
|
||||||
|
|
||||||
class CpplintTestBase(unittest.TestCase):
|
class CpplintTestBase(unittest.TestCase):
|
||||||
"""Provides some useful helper functions for cpplint tests."""
|
"""Provides some useful helper functions for cpplint tests."""
|
||||||
|
|
||||||
|
@ -147,7 +162,7 @@ class CpplintTestBase(unittest.TestCase):
|
||||||
function_state, error_collector)
|
function_state, error_collector)
|
||||||
return error_collector.Results()
|
return error_collector.Results()
|
||||||
|
|
||||||
def PerformIncludeWhatYouUse(self, code, filename='foo.h'):
|
def PerformIncludeWhatYouUse(self, code, filename='foo.h', io=codecs):
|
||||||
# First, build up the include state.
|
# First, build up the include state.
|
||||||
error_collector = ErrorCollector(self.assert_)
|
error_collector = ErrorCollector(self.assert_)
|
||||||
include_state = cpplint._IncludeState()
|
include_state = cpplint._IncludeState()
|
||||||
|
@ -163,7 +178,7 @@ class CpplintTestBase(unittest.TestCase):
|
||||||
|
|
||||||
# Second, look for missing includes.
|
# Second, look for missing includes.
|
||||||
cpplint.CheckForIncludeWhatYouUse(filename, lines, include_state,
|
cpplint.CheckForIncludeWhatYouUse(filename, lines, include_state,
|
||||||
error_collector)
|
error_collector, io)
|
||||||
return error_collector.Results()
|
return error_collector.Results()
|
||||||
|
|
||||||
# Perform lint and compare the error message with "expected_message".
|
# Perform lint and compare the error message with "expected_message".
|
||||||
|
@ -430,6 +445,15 @@ class CpplintTest(CpplintTestBase):
|
||||||
'Using sizeof(type). Use sizeof(varname) instead if possible'
|
'Using sizeof(type). Use sizeof(varname) instead if possible'
|
||||||
' [runtime/sizeof] [1]')
|
' [runtime/sizeof] [1]')
|
||||||
|
|
||||||
|
# Test false errors that happened with some include file names
|
||||||
|
def testIncludeFilenameFalseError(self):
|
||||||
|
self.TestLint(
|
||||||
|
'#include "foo/long-foo.h"',
|
||||||
|
'')
|
||||||
|
self.TestLint(
|
||||||
|
'#include "foo/sprintf.h"',
|
||||||
|
'')
|
||||||
|
|
||||||
# Test typedef cases. There was a bug that cpplint misidentified
|
# Test typedef cases. There was a bug that cpplint misidentified
|
||||||
# typedef for pointer to function as C-style cast and produced
|
# typedef for pointer to function as C-style cast and produced
|
||||||
# false-positive error messages.
|
# false-positive error messages.
|
||||||
|
@ -581,6 +605,75 @@ class CpplintTest(CpplintTestBase):
|
||||||
''',
|
''',
|
||||||
'')
|
'')
|
||||||
|
|
||||||
|
# Test the UpdateIncludeState code path.
|
||||||
|
mock_header_contents = ['#include "blah/foo.h"', '#include "blah/bar.h"']
|
||||||
|
message = self.PerformIncludeWhatYouUse(
|
||||||
|
'#include "blah/a.h"',
|
||||||
|
filename='blah/a.cc',
|
||||||
|
io=MockIo(mock_header_contents))
|
||||||
|
self.assertEquals(message, '')
|
||||||
|
|
||||||
|
mock_header_contents = ['#include <set>']
|
||||||
|
message = self.PerformIncludeWhatYouUse(
|
||||||
|
'''#include "blah/a.h"
|
||||||
|
std::set<int> foo;''',
|
||||||
|
filename='blah/a.cc',
|
||||||
|
io=MockIo(mock_header_contents))
|
||||||
|
self.assertEquals(message, '')
|
||||||
|
|
||||||
|
# Make sure we can find the correct header file if the cc file seems to be
|
||||||
|
# a temporary file generated by Emacs's flymake.
|
||||||
|
mock_header_contents = ['']
|
||||||
|
message = self.PerformIncludeWhatYouUse(
|
||||||
|
'''#include "blah/a.h"
|
||||||
|
std::set<int> foo;''',
|
||||||
|
filename='blah/a_flymake.cc',
|
||||||
|
io=MockIo(mock_header_contents))
|
||||||
|
self.assertEquals(message, 'Add #include <set> for set<> '
|
||||||
|
'[build/include_what_you_use] [4]')
|
||||||
|
|
||||||
|
# If there's just a cc and the header can't be found then it's ok.
|
||||||
|
message = self.PerformIncludeWhatYouUse(
|
||||||
|
'''#include "blah/a.h"
|
||||||
|
std::set<int> foo;''',
|
||||||
|
filename='blah/a.cc')
|
||||||
|
self.assertEquals(message, '')
|
||||||
|
|
||||||
|
# Make sure we find the headers with relative paths.
|
||||||
|
mock_header_contents = ['']
|
||||||
|
message = self.PerformIncludeWhatYouUse(
|
||||||
|
'''#include "%s/a.h"
|
||||||
|
std::set<int> foo;''' % os.path.basename(os.getcwd()),
|
||||||
|
filename='a.cc',
|
||||||
|
io=MockIo(mock_header_contents))
|
||||||
|
self.assertEquals(message, 'Add #include <set> for set<> '
|
||||||
|
'[build/include_what_you_use] [4]')
|
||||||
|
|
||||||
|
def testFilesBelongToSameModule(self):
|
||||||
|
f = cpplint.FilesBelongToSameModule
|
||||||
|
self.assertEquals((True, ''), f('a.cc', 'a.h'))
|
||||||
|
self.assertEquals((True, ''), f('base/google.cc', 'base/google.h'))
|
||||||
|
self.assertEquals((True, ''), f('base/google_test.cc', 'base/google.h'))
|
||||||
|
self.assertEquals((True, ''),
|
||||||
|
f('base/google_unittest.cc', 'base/google.h'))
|
||||||
|
self.assertEquals((True, ''),
|
||||||
|
f('base/internal/google_unittest.cc',
|
||||||
|
'base/public/google.h'))
|
||||||
|
self.assertEquals((True, 'xxx/yyy/'),
|
||||||
|
f('xxx/yyy/base/internal/google_unittest.cc',
|
||||||
|
'base/public/google.h'))
|
||||||
|
self.assertEquals((True, 'xxx/yyy/'),
|
||||||
|
f('xxx/yyy/base/google_unittest.cc',
|
||||||
|
'base/public/google.h'))
|
||||||
|
self.assertEquals((True, ''),
|
||||||
|
f('base/google_unittest.cc', 'base/google-inl.h'))
|
||||||
|
self.assertEquals((True, '/home/build/google3/'),
|
||||||
|
f('/home/build/google3/base/google.cc', 'base/google.h'))
|
||||||
|
|
||||||
|
self.assertEquals((False, ''),
|
||||||
|
f('/home/build/google3/base/google.cc', 'basu/google.h'))
|
||||||
|
self.assertEquals((False, ''), f('a.cc', 'b.h'))
|
||||||
|
|
||||||
def testCleanseLine(self):
|
def testCleanseLine(self):
|
||||||
self.assertEquals('int foo = 0; ',
|
self.assertEquals('int foo = 0; ',
|
||||||
cpplint.CleanseComments('int foo = 0; // danger!'))
|
cpplint.CleanseComments('int foo = 0; // danger!'))
|
||||||
|
@ -962,6 +1055,25 @@ class CpplintTest(CpplintTestBase):
|
||||||
'Consider using CHECK_LT instead of CHECK(a < b)'
|
'Consider using CHECK_LT instead of CHECK(a < b)'
|
||||||
' [readability/check] [2]')
|
' [readability/check] [2]')
|
||||||
|
|
||||||
|
self.TestLint('DCHECK(x == 42)',
|
||||||
|
'Consider using DCHECK_EQ instead of DCHECK(a == b)'
|
||||||
|
' [readability/check] [2]')
|
||||||
|
self.TestLint('DCHECK(x != 42)',
|
||||||
|
'Consider using DCHECK_NE instead of DCHECK(a != b)'
|
||||||
|
' [readability/check] [2]')
|
||||||
|
self.TestLint('DCHECK(x >= 42)',
|
||||||
|
'Consider using DCHECK_GE instead of DCHECK(a >= b)'
|
||||||
|
' [readability/check] [2]')
|
||||||
|
self.TestLint('DCHECK(x > 42)',
|
||||||
|
'Consider using DCHECK_GT instead of DCHECK(a > b)'
|
||||||
|
' [readability/check] [2]')
|
||||||
|
self.TestLint('DCHECK(x <= 42)',
|
||||||
|
'Consider using DCHECK_LE instead of DCHECK(a <= b)'
|
||||||
|
' [readability/check] [2]')
|
||||||
|
self.TestLint('DCHECK(x < 42)',
|
||||||
|
'Consider using DCHECK_LT instead of DCHECK(a < b)'
|
||||||
|
' [readability/check] [2]')
|
||||||
|
|
||||||
self.TestLint(
|
self.TestLint(
|
||||||
'EXPECT_TRUE("42" == x)',
|
'EXPECT_TRUE("42" == x)',
|
||||||
'Consider using EXPECT_EQ instead of EXPECT_TRUE(a == b)'
|
'Consider using EXPECT_EQ instead of EXPECT_TRUE(a == b)'
|
||||||
|
@ -1088,9 +1200,12 @@ class CpplintTest(CpplintTestBase):
|
||||||
' [whitespace/parens] [5]')
|
' [whitespace/parens] [5]')
|
||||||
self.TestLint('switch ( foo) {', 'Mismatching spaces inside () in switch'
|
self.TestLint('switch ( foo) {', 'Mismatching spaces inside () in switch'
|
||||||
' [whitespace/parens] [5]')
|
' [whitespace/parens] [5]')
|
||||||
|
self.TestLint('for (foo; ba; bar ) {', 'Mismatching spaces inside () in for'
|
||||||
|
' [whitespace/parens] [5]')
|
||||||
self.TestLint('for (; foo; bar) {', '')
|
self.TestLint('for (; foo; bar) {', '')
|
||||||
self.TestLint('for ( ; foo; bar) {', '')
|
self.TestLint('for ( ; foo; bar) {', '')
|
||||||
self.TestLint('for ( ; foo; bar ) {', '')
|
self.TestLint('for ( ; foo; bar ) {', '')
|
||||||
|
self.TestLint('for (foo; bar; ) {', '')
|
||||||
self.TestLint('while ( foo ) {', 'Should have zero or one spaces inside'
|
self.TestLint('while ( foo ) {', 'Should have zero or one spaces inside'
|
||||||
' ( and ) in while [whitespace/parens] [5]')
|
' ( and ) in while [whitespace/parens] [5]')
|
||||||
|
|
||||||
|
@ -1285,6 +1400,13 @@ class CpplintTest(CpplintTestBase):
|
||||||
self.TestLint('//x', 'Should have a space between // and comment'
|
self.TestLint('//x', 'Should have a space between // and comment'
|
||||||
' [whitespace/comments] [4]')
|
' [whitespace/comments] [4]')
|
||||||
self.TestLint('// x', '')
|
self.TestLint('// x', '')
|
||||||
|
self.TestLint('//----', '')
|
||||||
|
self.TestLint('//====', '')
|
||||||
|
self.TestLint('//////', '')
|
||||||
|
self.TestLint('////// x', '')
|
||||||
|
self.TestLint('/// x', '')
|
||||||
|
self.TestLint('////x', 'Should have a space between // and comment'
|
||||||
|
' [whitespace/comments] [4]')
|
||||||
|
|
||||||
# Test a line preceded by empty or comment lines. There was a bug
|
# Test a line preceded by empty or comment lines. There was a bug
|
||||||
# that caused it to print the same warning N times if the erroneous
|
# that caused it to print the same warning N times if the erroneous
|
||||||
|
@ -1529,6 +1651,24 @@ class CpplintTest(CpplintTestBase):
|
||||||
finally:
|
finally:
|
||||||
cpplint._cpplint_state.filters = old_filters
|
cpplint._cpplint_state.filters = old_filters
|
||||||
|
|
||||||
|
def testDefaultFilter(self):
|
||||||
|
default_filters = cpplint._DEFAULT_FILTERS
|
||||||
|
old_filters = cpplint._cpplint_state.filters
|
||||||
|
cpplint._DEFAULT_FILTERS = [ '-whitespace' ]
|
||||||
|
try:
|
||||||
|
# Reset filters
|
||||||
|
cpplint._cpplint_state.SetFilters('')
|
||||||
|
self.TestLint('// Hello there ', '')
|
||||||
|
cpplint._cpplint_state.SetFilters('+whitespace/end_of_line')
|
||||||
|
self.TestLint(
|
||||||
|
'// Hello there ',
|
||||||
|
'Line ends in whitespace. Consider deleting these extra spaces.'
|
||||||
|
' [whitespace/end_of_line] [4]')
|
||||||
|
self.TestLint(' weird opening space', '')
|
||||||
|
finally:
|
||||||
|
cpplint._cpplint_state.filters = old_filters
|
||||||
|
cpplint._DEFAULT_FILTERS = default_filters
|
||||||
|
|
||||||
def testUnnamedNamespacesInHeaders(self):
|
def testUnnamedNamespacesInHeaders(self):
|
||||||
self.TestLanguageRulesCheck(
|
self.TestLanguageRulesCheck(
|
||||||
'foo.h', 'namespace {',
|
'foo.h', 'namespace {',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user