| Index: cpplint.py
|
| diff --git a/cpplint.py b/cpplint.py
|
| index 89b7ece9d41762f7fcd4509f82e724c6bf530a83..8bd90c2c30b083cf493e15df3d72878776db9289 100755
|
| --- a/cpplint.py
|
| +++ b/cpplint.py
|
| @@ -28,40 +28,6 @@
|
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
| -# Here are some issues that I've had people identify in my code during reviews,
|
| -# that I think are possible to flag automatically in a lint tool. If these were
|
| -# caught by lint, it would save time both for myself and that of my reviewers.
|
| -# Most likely, some of these are beyond the scope of the current lint framework,
|
| -# but I think it is valuable to retain these wish-list items even if they cannot
|
| -# be immediately implemented.
|
| -#
|
| -# Suggestions
|
| -# -----------
|
| -# - Check for no 'explicit' for multi-arg ctor
|
| -# - Check for boolean assign RHS in parens
|
| -# - Check for ctor initializer-list colon position and spacing
|
| -# - Check that if there's a ctor, there should be a dtor
|
| -# - Check accessors that return non-pointer member variables are
|
| -# declared const
|
| -# - Check accessors that return non-const pointer member vars are
|
| -# *not* declared const
|
| -# - Check for using public includes for testing
|
| -# - Check for spaces between brackets in one-line inline method
|
| -# - Check for no assert()
|
| -# - Check for spaces surrounding operators
|
| -# - Check for 0 in pointer context (should be NULL)
|
| -# - Check for 0 in char context (should be '\0')
|
| -# - Check for camel-case method name conventions for methods
|
| -# that are not simple inline getters and setters
|
| -# - Do not indent namespace contents
|
| -# - Avoid inlining non-trivial constructors in header files
|
| -# - Check for old-school (void) cast for call-sites of functions
|
| -# ignored return value
|
| -# - Check gUnit usage of anonymous namespace
|
| -# - Check for class declaration order (typedefs, consts, enums,
|
| -# ctor(s?), dtor, friend declarations, methods, member vars)
|
| -#
|
| -
|
| """Does google-lint on c++ files.
|
|
|
| The goal of this script is to identify places in the code that *may*
|
| @@ -89,7 +55,8 @@ import unicodedata
|
|
|
| _USAGE = """
|
| Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...]
|
| - [--counting=total|toplevel|detailed]
|
| + [--counting=total|toplevel|detailed] [--root=subdir]
|
| + [--linelength=digits]
|
| <file> [file] ...
|
|
|
| The style guidelines this tries to follow are those in
|
| @@ -104,7 +71,8 @@ Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...]
|
| suppresses errors of all categories on that line.
|
|
|
| The files passed in will be linted; at least one file must be provided.
|
| - Linted extensions are .cc, .cpp, and .h. Other file types will be ignored.
|
| + Default linted extensions are .cc, .cpp, .cu, .cuh and .h. Change the
|
| + extensions with the --extensions flag.
|
|
|
| Flags:
|
|
|
| @@ -152,13 +120,25 @@ Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...]
|
| No flag => CHROME_BROWSER_UI_BROWSER_H_
|
| --root=chrome => BROWSER_UI_BROWSER_H_
|
| --root=chrome/browser => UI_BROWSER_H_
|
| +
|
| + linelength=digits
|
| + This is the allowed line length for the project. The default value is
|
| + 80 characters.
|
| +
|
| + Examples:
|
| + --linelength=120
|
| +
|
| + extensions=extension,extension,...
|
| + The allowed file extensions that cpplint will check
|
| +
|
| + Examples:
|
| + --extensions=hpp,cpp
|
| """
|
|
|
| # We categorize each error message we print. Here are the categories.
|
| # We want an explicit list so we can list them all in cpplint --filter=.
|
| # If you add a new error message with a new category, add it to the list
|
| # here! cpplint_unittest.py should tell you if you forget to do this.
|
| -# \ used for clearer layout -- pylint: disable-msg=C6013
|
| _ERROR_CATEGORIES = [
|
| 'build/class',
|
| 'build/deprecated',
|
| @@ -185,6 +165,7 @@ _ERROR_CATEGORIES = [
|
| 'readability/multiline_string',
|
| 'readability/namespace',
|
| 'readability/nolint',
|
| + 'readability/nul',
|
| 'readability/streams',
|
| 'readability/todo',
|
| 'readability/utf8',
|
| @@ -200,20 +181,19 @@ _ERROR_CATEGORIES = [
|
| 'runtime/printf',
|
| 'runtime/printf_format',
|
| 'runtime/references',
|
| - 'runtime/rtti',
|
| - 'runtime/sizeof',
|
| 'runtime/string',
|
| 'runtime/threadsafe_fn',
|
| + 'runtime/vlog',
|
| 'whitespace/blank_line',
|
| 'whitespace/braces',
|
| 'whitespace/comma',
|
| 'whitespace/comments',
|
| + 'whitespace/empty_conditional_body',
|
| 'whitespace/empty_loop_body',
|
| 'whitespace/end_of_line',
|
| 'whitespace/ending_newline',
|
| 'whitespace/forcolon',
|
| 'whitespace/indent',
|
| - 'whitespace/labels',
|
| 'whitespace/line_length',
|
| 'whitespace/newline',
|
| 'whitespace/operators',
|
| @@ -233,35 +213,143 @@ _DEFAULT_FILTERS = ['-build/include_alpha']
|
| # decided those were OK, as long as they were in UTF-8 and didn't represent
|
| # hard-coded international strings, which belong in a separate i18n file.
|
|
|
| -# Headers that we consider STL headers.
|
| -_STL_HEADERS = frozenset([
|
| - 'algobase.h', 'algorithm', 'alloc.h', 'bitset', 'deque', 'exception',
|
| - 'function.h', 'functional', 'hash_map', 'hash_map.h', 'hash_set',
|
| - 'hash_set.h', 'iterator', 'list', 'list.h', 'map', 'memory', 'new',
|
| - 'pair.h', 'pthread_alloc', 'queue', 'set', 'set.h', 'sstream', 'stack',
|
| - 'stl_alloc.h', 'stl_relops.h', 'type_traits.h',
|
| - 'utility', 'vector', 'vector.h',
|
| - ])
|
| -
|
|
|
| -# Non-STL C++ system headers.
|
| +# C++ headers
|
| _CPP_HEADERS = frozenset([
|
| - 'algo.h', 'builtinbuf.h', 'bvector.h', 'cassert', 'cctype',
|
| - 'cerrno', 'cfloat', 'ciso646', 'climits', 'clocale', 'cmath',
|
| - 'complex', 'complex.h', 'csetjmp', 'csignal', 'cstdarg', 'cstddef',
|
| - 'cstdio', 'cstdlib', 'cstring', 'ctime', 'cwchar', 'cwctype',
|
| - 'defalloc.h', 'deque.h', 'editbuf.h', 'exception', 'fstream',
|
| - 'fstream.h', 'hashtable.h', 'heap.h', 'indstream.h', 'iomanip',
|
| - 'iomanip.h', 'ios', 'iosfwd', 'iostream', 'iostream.h', 'istream',
|
| - 'istream.h', 'iterator.h', 'limits', 'map.h', 'multimap.h', 'multiset.h',
|
| - 'numeric', 'ostream', 'ostream.h', 'parsestream.h', 'pfstream.h',
|
| - 'PlotFile.h', 'procbuf.h', 'pthread_alloc.h', 'rope', 'rope.h',
|
| - 'ropeimpl.h', 'SFile.h', 'slist', 'slist.h', 'stack.h', 'stdexcept',
|
| - 'stdiostream.h', 'streambuf.h', 'stream.h', 'strfile.h', 'string',
|
| - 'strstream', 'strstream.h', 'tempbuf.h', 'tree.h', 'typeinfo', 'valarray',
|
| + # Legacy
|
| + 'algobase.h',
|
| + 'algo.h',
|
| + 'alloc.h',
|
| + 'builtinbuf.h',
|
| + 'bvector.h',
|
| + 'complex.h',
|
| + 'defalloc.h',
|
| + 'deque.h',
|
| + 'editbuf.h',
|
| + 'fstream.h',
|
| + 'function.h',
|
| + 'hash_map',
|
| + 'hash_map.h',
|
| + 'hash_set',
|
| + 'hash_set.h',
|
| + 'hashtable.h',
|
| + 'heap.h',
|
| + 'indstream.h',
|
| + 'iomanip.h',
|
| + 'iostream.h',
|
| + 'istream.h',
|
| + 'iterator.h',
|
| + 'list.h',
|
| + 'map.h',
|
| + 'multimap.h',
|
| + 'multiset.h',
|
| + 'ostream.h',
|
| + 'pair.h',
|
| + 'parsestream.h',
|
| + 'pfstream.h',
|
| + 'procbuf.h',
|
| + 'pthread_alloc',
|
| + 'pthread_alloc.h',
|
| + 'rope',
|
| + 'rope.h',
|
| + 'ropeimpl.h',
|
| + 'set.h',
|
| + 'slist',
|
| + 'slist.h',
|
| + 'stack.h',
|
| + 'stdiostream.h',
|
| + 'stl_alloc.h',
|
| + 'stl_relops.h',
|
| + 'streambuf.h',
|
| + 'stream.h',
|
| + 'strfile.h',
|
| + 'strstream.h',
|
| + 'tempbuf.h',
|
| + 'tree.h',
|
| + 'type_traits.h',
|
| + 'vector.h',
|
| + # 17.6.1.2 C++ library headers
|
| + 'algorithm',
|
| + 'array',
|
| + 'atomic',
|
| + 'bitset',
|
| + 'chrono',
|
| + 'codecvt',
|
| + 'complex',
|
| + 'condition_variable',
|
| + 'deque',
|
| + 'exception',
|
| + 'forward_list',
|
| + 'fstream',
|
| + 'functional',
|
| + 'future',
|
| + 'initializer_list',
|
| + 'iomanip',
|
| + 'ios',
|
| + 'iosfwd',
|
| + 'iostream',
|
| + 'istream',
|
| + 'iterator',
|
| + 'limits',
|
| + 'list',
|
| + 'locale',
|
| + 'map',
|
| + 'memory',
|
| + 'mutex',
|
| + 'new',
|
| + 'numeric',
|
| + 'ostream',
|
| + 'queue',
|
| + 'random',
|
| + 'ratio',
|
| + 'regex',
|
| + 'set',
|
| + 'sstream',
|
| + 'stack',
|
| + 'stdexcept',
|
| + 'streambuf',
|
| + 'string',
|
| + 'strstream',
|
| + 'system_error',
|
| + 'thread',
|
| + 'tuple',
|
| + 'typeindex',
|
| + 'typeinfo',
|
| + 'type_traits',
|
| + 'unordered_map',
|
| + 'unordered_set',
|
| + 'utility',
|
| + 'valarray',
|
| + 'vector',
|
| + # 17.6.1.2 C++ headers for C library facilities
|
| + 'cassert',
|
| + 'ccomplex',
|
| + 'cctype',
|
| + 'cerrno',
|
| + 'cfenv',
|
| + 'cfloat',
|
| + 'cinttypes',
|
| + 'ciso646',
|
| + 'climits',
|
| + 'clocale',
|
| + 'cmath',
|
| + 'csetjmp',
|
| + 'csignal',
|
| + 'cstdalign',
|
| + 'cstdarg',
|
| + 'cstdbool',
|
| + 'cstddef',
|
| + 'cstdint',
|
| + 'cstdio',
|
| + 'cstdlib',
|
| + 'cstring',
|
| + 'ctgmath',
|
| + 'ctime',
|
| + 'cuchar',
|
| + 'cwchar',
|
| + 'cwctype',
|
| ])
|
|
|
| -
|
| # Assertion macros. These are defined in base/logging.h and
|
| # testing/base/gunit.h. Note that the _M versions need to come first
|
| # for substring matching to work.
|
| @@ -316,9 +404,8 @@ _ALT_TOKEN_REPLACEMENT = {
|
| # Compile regular expression that matches all the above keywords. The "[ =()]"
|
| # bit is meant to avoid matching these keywords outside of boolean expressions.
|
| #
|
| -# False positives include C-style multi-line comments (http://go/nsiut )
|
| -# and multi-line strings (http://go/beujw ), but those have always been
|
| -# troublesome for cpplint.
|
| +# False positives include C-style multi-line comments and multi-line strings
|
| +# but those have always been troublesome for cpplint.
|
| _ALT_TOKEN_REPLACEMENT_PATTERN = re.compile(
|
| r'[ =()](' + ('|'.join(_ALT_TOKEN_REPLACEMENT.keys())) + r')(?=[ (]|$)')
|
|
|
| @@ -356,6 +443,14 @@ _error_suppressions = {}
|
| # This is set by --root flag.
|
| _root = None
|
|
|
| +# The allowed line length of files.
|
| +# This is set by --linelength flag.
|
| +_line_length = 80
|
| +
|
| +# The allowed extensions for file names
|
| +# This is set by --extensions flag.
|
| +_valid_extensions = set(['cc', 'h', 'cpp', 'cu', 'cuh'])
|
| +
|
| def ParseNolintSuppressions(filename, raw_line, linenum, error):
|
| """Updates the global list of error-suppressions.
|
|
|
| @@ -410,14 +505,32 @@ def Match(pattern, s):
|
| # The regexp compilation caching is inlined in both Match and Search for
|
| # performance reasons; factoring it out into a separate function turns out
|
| # to be noticeably expensive.
|
| - if not pattern in _regexp_compile_cache:
|
| + if pattern not in _regexp_compile_cache:
|
| _regexp_compile_cache[pattern] = sre_compile.compile(pattern)
|
| return _regexp_compile_cache[pattern].match(s)
|
|
|
|
|
| +def ReplaceAll(pattern, rep, s):
|
| + """Replaces instances of pattern in a string with a replacement.
|
| +
|
| + The compiled regex is kept in a cache shared by Match and Search.
|
| +
|
| + Args:
|
| + pattern: regex pattern
|
| + rep: replacement text
|
| + s: search string
|
| +
|
| + Returns:
|
| + string with replacements made (or original string if no replacements)
|
| + """
|
| + if pattern not in _regexp_compile_cache:
|
| + _regexp_compile_cache[pattern] = sre_compile.compile(pattern)
|
| + return _regexp_compile_cache[pattern].sub(rep, s)
|
| +
|
| +
|
| def Search(pattern, s):
|
| """Searches the string for the pattern, caching the compiled regexp."""
|
| - if not pattern in _regexp_compile_cache:
|
| + if pattern not in _regexp_compile_cache:
|
| _regexp_compile_cache[pattern] = sre_compile.compile(pattern)
|
| return _regexp_compile_cache[pattern].search(s)
|
|
|
| @@ -458,11 +571,17 @@ class _IncludeState(dict):
|
|
|
| def __init__(self):
|
| dict.__init__(self)
|
| + self.ResetSection()
|
| +
|
| + def ResetSection(self):
|
| # The name of the current section.
|
| self._section = self._INITIAL_SECTION
|
| # The path of last found header.
|
| self._last_header = ''
|
|
|
| + def SetLastHeader(self, header_path):
|
| + self._last_header = header_path
|
| +
|
| def CanonicalizeAlphabeticalOrder(self, header_path):
|
| """Returns a path canonicalized for alphabetical comparison.
|
|
|
| @@ -478,19 +597,25 @@ class _IncludeState(dict):
|
| """
|
| return header_path.replace('-inl.h', '.h').replace('-', '_').lower()
|
|
|
| - def IsInAlphabeticalOrder(self, header_path):
|
| + def IsInAlphabeticalOrder(self, clean_lines, linenum, header_path):
|
| """Check if a header is in alphabetical order with the previous header.
|
|
|
| Args:
|
| - header_path: Header to be checked.
|
| + clean_lines: A CleansedLines instance containing the file.
|
| + linenum: The number of the line to check.
|
| + header_path: Canonicalized header to be checked.
|
|
|
| Returns:
|
| Returns true if the header is in alphabetical order.
|
| """
|
| - canonical_header = self.CanonicalizeAlphabeticalOrder(header_path)
|
| - if self._last_header > canonical_header:
|
| + # If previous section is different from current section, _last_header will
|
| + # be reset to empty string, so it's always less than current header.
|
| + #
|
| + # If previous line was a blank line, assume that the headers are
|
| + # intentionally sorted the way they are.
|
| + if (self._last_header > header_path and
|
| + not Match(r'^\s*$', clean_lines.elided[linenum - 1])):
|
| return False
|
| - self._last_header = canonical_header
|
| return True
|
|
|
| def CheckNextIncludeOrder(self, header_type):
|
| @@ -883,7 +1008,7 @@ def Error(filename, linenum, category, confidence, message):
|
| filename, linenum, message, category, confidence))
|
|
|
|
|
| -# Matches standard C++ escape esequences per 2.13.2.3 of the C++ standard.
|
| +# Matches standard C++ escape sequences per 2.13.2.3 of the C++ standard.
|
| _RE_PATTERN_CLEANSE_LINE_ESCAPES = re.compile(
|
| r'\\([abfnrtv?"\\\']|\d+|x[0-9a-fA-F]+)')
|
| # Matches strings. Escape codes should already be removed by ESCAPES.
|
| @@ -922,6 +1047,67 @@ def IsCppString(line):
|
| return ((line.count('"') - line.count(r'\"') - line.count("'\"'")) & 1) == 1
|
|
|
|
|
| +def CleanseRawStrings(raw_lines):
|
| + """Removes C++11 raw strings from lines.
|
| +
|
| + Before:
|
| + static const char kData[] = R"(
|
| + multi-line string
|
| + )";
|
| +
|
| + After:
|
| + static const char kData[] = ""
|
| + (replaced by blank line)
|
| + "";
|
| +
|
| + Args:
|
| + raw_lines: list of raw lines.
|
| +
|
| + Returns:
|
| + list of lines with C++11 raw strings replaced by empty strings.
|
| + """
|
| +
|
| + delimiter = None
|
| + lines_without_raw_strings = []
|
| + for line in raw_lines:
|
| + if delimiter:
|
| + # Inside a raw string, look for the end
|
| + end = line.find(delimiter)
|
| + if end >= 0:
|
| + # Found the end of the string, match leading space for this
|
| + # line and resume copying the original lines, and also insert
|
| + # a "" on the last line.
|
| + leading_space = Match(r'^(\s*)\S', line)
|
| + line = leading_space.group(1) + '""' + line[end + len(delimiter):]
|
| + delimiter = None
|
| + else:
|
| + # Haven't found the end yet, append a blank line.
|
| + line = ''
|
| +
|
| + else:
|
| + # Look for beginning of a raw string.
|
| + # See 2.14.15 [lex.string] for syntax.
|
| + matched = Match(r'^(.*)\b(?:R|u8R|uR|UR|LR)"([^\s\\()]*)\((.*)$', line)
|
| + if matched:
|
| + delimiter = ')' + matched.group(2) + '"'
|
| +
|
| + end = matched.group(3).find(delimiter)
|
| + if end >= 0:
|
| + # Raw string ended on same line
|
| + line = (matched.group(1) + '""' +
|
| + matched.group(3)[end + len(delimiter):])
|
| + delimiter = None
|
| + else:
|
| + # Start of a multi-line raw string
|
| + line = matched.group(1) + '""'
|
| +
|
| + lines_without_raw_strings.append(line)
|
| +
|
| + # TODO(unknown): if delimiter is not None here, we might want to
|
| + # emit a warning for unterminated string.
|
| + return lines_without_raw_strings
|
| +
|
| +
|
| def FindNextMultiLineCommentStart(lines, lineix):
|
| """Find the beginning marker for a multiline comment."""
|
| while lineix < len(lines):
|
| @@ -996,9 +1182,11 @@ class CleansedLines(object):
|
| self.lines = []
|
| self.raw_lines = lines
|
| self.num_lines = len(lines)
|
| - for linenum in range(len(lines)):
|
| - self.lines.append(CleanseComments(lines[linenum]))
|
| - elided = self._CollapseStrings(lines[linenum])
|
| + self.lines_without_raw_strings = CleanseRawStrings(lines)
|
| + for linenum in range(len(self.lines_without_raw_strings)):
|
| + self.lines.append(CleanseComments(
|
| + self.lines_without_raw_strings[linenum]))
|
| + elided = self._CollapseStrings(self.lines_without_raw_strings[linenum])
|
| self.elided.append(CleanseComments(elided))
|
|
|
| def NumLines(self):
|
| @@ -1038,7 +1226,8 @@ def FindEndOfExpressionInLine(line, startpos, depth, startchar, endchar):
|
| endchar: expression closing character.
|
|
|
| Returns:
|
| - Index just after endchar.
|
| + On finding matching endchar: (index just after matching endchar, 0)
|
| + Otherwise: (-1, new depth at end of this line)
|
| """
|
| for i in xrange(startpos, len(line)):
|
| if line[i] == startchar:
|
| @@ -1046,14 +1235,14 @@ def FindEndOfExpressionInLine(line, startpos, depth, startchar, endchar):
|
| elif line[i] == endchar:
|
| depth -= 1
|
| if depth == 0:
|
| - return i + 1
|
| - return -1
|
| + return (i + 1, 0)
|
| + return (-1, depth)
|
|
|
|
|
| def CloseExpression(clean_lines, linenum, pos):
|
| - """If input points to ( or { or [, finds the position that closes it.
|
| + """If input points to ( or { or [ or <, finds the position that closes it.
|
|
|
| - If lines[linenum][pos] points to a '(' or '{' or '[', finds the
|
| + If lines[linenum][pos] points to a '(' or '{' or '[' or '<', finds the
|
| linenum/pos that correspond to the closing of the expression.
|
|
|
| Args:
|
| @@ -1070,30 +1259,104 @@ def CloseExpression(clean_lines, linenum, pos):
|
|
|
| line = clean_lines.elided[linenum]
|
| startchar = line[pos]
|
| - if startchar not in '({[':
|
| + if startchar not in '({[<':
|
| return (line, clean_lines.NumLines(), -1)
|
| if startchar == '(': endchar = ')'
|
| if startchar == '[': endchar = ']'
|
| if startchar == '{': endchar = '}'
|
| + if startchar == '<': endchar = '>'
|
|
|
| # Check first line
|
| - end_pos = FindEndOfExpressionInLine(line, pos, 0, startchar, endchar)
|
| + (end_pos, num_open) = FindEndOfExpressionInLine(
|
| + line, pos, 0, startchar, endchar)
|
| if end_pos > -1:
|
| return (line, linenum, end_pos)
|
| - tail = line[pos:]
|
| - num_open = tail.count(startchar) - tail.count(endchar)
|
| +
|
| + # Continue scanning forward
|
| while linenum < clean_lines.NumLines() - 1:
|
| linenum += 1
|
| line = clean_lines.elided[linenum]
|
| - delta = line.count(startchar) - line.count(endchar)
|
| - if num_open + delta <= 0:
|
| - return (line, linenum,
|
| - FindEndOfExpressionInLine(line, 0, num_open, startchar, endchar))
|
| - num_open += delta
|
| + (end_pos, num_open) = FindEndOfExpressionInLine(
|
| + line, 0, num_open, startchar, endchar)
|
| + if end_pos > -1:
|
| + return (line, linenum, end_pos)
|
|
|
| # Did not find endchar before end of file, give up
|
| return (line, clean_lines.NumLines(), -1)
|
|
|
| +
|
| +def FindStartOfExpressionInLine(line, endpos, depth, startchar, endchar):
|
| + """Find position at the matching startchar.
|
| +
|
| + This is almost the reverse of FindEndOfExpressionInLine, but note
|
| + that the input position and returned position differs by 1.
|
| +
|
| + Args:
|
| + line: a CleansedLines line.
|
| + endpos: start searching at this position.
|
| + depth: nesting level at endpos.
|
| + startchar: expression opening character.
|
| + endchar: expression closing character.
|
| +
|
| + Returns:
|
| + On finding matching startchar: (index at matching startchar, 0)
|
| + Otherwise: (-1, new depth at beginning of this line)
|
| + """
|
| + for i in xrange(endpos, -1, -1):
|
| + if line[i] == endchar:
|
| + depth += 1
|
| + elif line[i] == startchar:
|
| + depth -= 1
|
| + if depth == 0:
|
| + return (i, 0)
|
| + return (-1, depth)
|
| +
|
| +
|
| +def ReverseCloseExpression(clean_lines, linenum, pos):
|
| + """If input points to ) or } or ] or >, finds the position that opens it.
|
| +
|
| + If lines[linenum][pos] points to a ')' or '}' or ']' or '>', finds the
|
| + linenum/pos that correspond to the opening of the expression.
|
| +
|
| + Args:
|
| + clean_lines: A CleansedLines instance containing the file.
|
| + linenum: The number of the line to check.
|
| + pos: A position on the line.
|
| +
|
| + Returns:
|
| + A tuple (line, linenum, pos) pointer *at* the opening brace, or
|
| + (line, 0, -1) if we never find the matching opening brace. Note
|
| + we ignore strings and comments when matching; and the line we
|
| + return is the 'cleansed' line at linenum.
|
| + """
|
| + line = clean_lines.elided[linenum]
|
| + endchar = line[pos]
|
| + if endchar not in ')}]>':
|
| + return (line, 0, -1)
|
| + if endchar == ')': startchar = '('
|
| + if endchar == ']': startchar = '['
|
| + if endchar == '}': startchar = '{'
|
| + if endchar == '>': startchar = '<'
|
| +
|
| + # Check last line
|
| + (start_pos, num_open) = FindStartOfExpressionInLine(
|
| + line, pos, 0, startchar, endchar)
|
| + if start_pos > -1:
|
| + return (line, linenum, start_pos)
|
| +
|
| + # Continue scanning backward
|
| + while linenum > 0:
|
| + linenum -= 1
|
| + line = clean_lines.elided[linenum]
|
| + (start_pos, num_open) = FindStartOfExpressionInLine(
|
| + line, len(line) - 1, num_open, startchar, endchar)
|
| + if start_pos > -1:
|
| + return (line, linenum, start_pos)
|
| +
|
| + # Did not find startchar before beginning of file, give up
|
| + return (line, 0, -1)
|
| +
|
| +
|
| def CheckForCopyright(filename, lines, error):
|
| """Logs an error if no Copyright message appears at the top of the file."""
|
|
|
| @@ -1206,13 +1469,17 @@ def CheckForHeaderGuard(filename, lines, error):
|
| '#endif line should be "#endif // %s"' % cppvar)
|
|
|
|
|
| -def CheckForUnicodeReplacementCharacters(filename, lines, error):
|
| - """Logs an error for each line containing Unicode replacement characters.
|
| +def CheckForBadCharacters(filename, lines, error):
|
| + """Logs an error for each line containing bad characters.
|
|
|
| - These indicate that either the file contained invalid UTF-8 (likely)
|
| - or Unicode replacement characters (which it shouldn't). Note that
|
| - it's possible for this to throw off line numbering if the invalid
|
| - UTF-8 occurred adjacent to a newline.
|
| + Two kinds of bad characters:
|
| +
|
| + 1. Unicode replacement characters: These indicate that either the file
|
| + contained invalid UTF-8 (likely) or Unicode replacement characters (which
|
| + it shouldn't). Note that it's possible for this to throw off line
|
| + numbering if the invalid UTF-8 occurred adjacent to a newline.
|
| +
|
| + 2. NUL bytes. These are problematic for some tools.
|
|
|
| Args:
|
| filename: The name of the current file.
|
| @@ -1223,6 +1490,8 @@ def CheckForUnicodeReplacementCharacters(filename, lines, error):
|
| if u'\ufffd' in line:
|
| error(filename, linenum, 'readability/utf8', 5,
|
| 'Line contains invalid UTF-8 (or Unicode replacement character).')
|
| + if '\0' in line:
|
| + error(filename, linenum, 'readability/nul', 5, 'Line contains NUL byte.')
|
|
|
|
|
| def CheckForNewlineAtEOF(filename, lines, error):
|
| @@ -1277,8 +1546,8 @@ def CheckForMultilineCommentsAndStrings(filename, clean_lines, linenum, error):
|
| if (line.count('"') - line.count('\\"')) % 2:
|
| error(filename, linenum, 'readability/multiline_string', 5,
|
| 'Multi-line string ("...") found. This lint script doesn\'t '
|
| - 'do well with such strings, and may give bogus warnings. They\'re '
|
| - 'ugly and unnecessary, and you should use concatenation instead".')
|
| + 'do well with such strings, and may give bogus warnings. '
|
| + 'Use C++11 raw strings or concatenation instead.')
|
|
|
|
|
| threading_list = (
|
| @@ -1292,7 +1561,6 @@ threading_list = (
|
| ('gmtime(', 'gmtime_r('),
|
| ('localtime(', 'localtime_r('),
|
| ('rand(', 'rand_r('),
|
| - ('readdir(', 'readdir_r('),
|
| ('strtok(', 'strtok_r('),
|
| ('ttyname(', 'ttyname_r('),
|
| )
|
| @@ -1316,7 +1584,7 @@ def CheckPosixThreading(filename, clean_lines, linenum, error):
|
| line = clean_lines.elided[linenum]
|
| for single_thread_function, multithread_safe_function in threading_list:
|
| ix = line.find(single_thread_function)
|
| - # Comparisons made explicit for clarity -- pylint: disable-msg=C6403
|
| + # Comparisons made explicit for clarity -- pylint: disable=g-explicit-bool-comparison
|
| if ix >= 0 and (ix == 0 or (not line[ix - 1].isalnum() and
|
| line[ix - 1] not in ('_', '.', '>'))):
|
| error(filename, linenum, 'runtime/threadsafe_fn', 2,
|
| @@ -1325,6 +1593,25 @@ def CheckPosixThreading(filename, clean_lines, linenum, error):
|
| '...) for improved thread safety.')
|
|
|
|
|
| +def CheckVlogArguments(filename, clean_lines, linenum, error):
|
| + """Checks that VLOG() is only used for defining a logging level.
|
| +
|
| + For example, VLOG(2) is correct. VLOG(INFO), VLOG(WARNING), VLOG(ERROR), and
|
| + VLOG(FATAL) are not.
|
| +
|
| + Args:
|
| + filename: The name of the current file.
|
| + clean_lines: A CleansedLines instance containing the file.
|
| + linenum: The number of the line to check.
|
| + error: The function to call with any errors found.
|
| + """
|
| + line = clean_lines.elided[linenum]
|
| + if Search(r'\bVLOG\((INFO|ERROR|WARNING|DFATAL|FATAL)\)', line):
|
| + error(filename, linenum, 'runtime/vlog', 5,
|
| + 'VLOG() should be used with numeric verbosity level. '
|
| + 'Use LOG() if you want symbolic severity levels.')
|
| +
|
| +
|
| # Matches invalid increment: *count++, which moves pointer instead of
|
| # incrementing a value.
|
| _RE_PATTERN_INVALID_INCREMENT = re.compile(
|
| @@ -1400,8 +1687,18 @@ class _ClassInfo(_BlockInfo):
|
| self.is_derived = False
|
| if class_or_struct == 'struct':
|
| self.access = 'public'
|
| + self.is_struct = True
|
| else:
|
| self.access = 'private'
|
| + self.is_struct = False
|
| +
|
| + # Remember initial indentation level for this class. Using raw_lines here
|
| + # instead of elided to account for leading comments.
|
| + initial_indent = Match(r'^( *)\S', clean_lines.raw_lines[linenum])
|
| + if initial_indent:
|
| + self.class_indent = len(initial_indent.group(1))
|
| + else:
|
| + self.class_indent = 0
|
|
|
| # Try to find the end of the class. This will be confused by things like:
|
| # class A {
|
| @@ -1422,6 +1719,19 @@ class _ClassInfo(_BlockInfo):
|
| if Search('(^|[^:]):($|[^:])', clean_lines.elided[linenum]):
|
| self.is_derived = True
|
|
|
| + def CheckEnd(self, filename, clean_lines, linenum, error):
|
| + # Check that closing brace is aligned with beginning of the class.
|
| + # Only do this if the closing brace is indented by only whitespaces.
|
| + # This means we will not check single-line class definitions.
|
| + indent = Match(r'^( *)\}', clean_lines.elided[linenum])
|
| + if indent and len(indent.group(1)) != self.class_indent:
|
| + if self.is_struct:
|
| + parent = 'struct ' + self.name
|
| + else:
|
| + parent = 'class ' + self.name
|
| + error(filename, linenum, 'whitespace/indent', 3,
|
| + 'Closing brace should be aligned with beginning of %s' % parent)
|
| +
|
|
|
| class _NamespaceInfo(_BlockInfo):
|
| """Stores information about a namespace."""
|
| @@ -1454,14 +1764,14 @@ class _NamespaceInfo(_BlockInfo):
|
| #
|
| # Note that we accept C style "/* */" comments for terminating
|
| # namespaces, so that code that terminate namespaces inside
|
| - # preprocessor macros can be cpplint clean. Example: http://go/nxpiz
|
| + # preprocessor macros can be cpplint clean.
|
| #
|
| # We also accept stuff like "// end of namespace <name>." with the
|
| # period at the end.
|
| #
|
| # Besides these, we don't accept anything else, otherwise we might
|
| # get false negatives when existing comment is a substring of the
|
| - # expected namespace. Example: http://go/ldkdc, http://cl/23548205
|
| + # expected namespace.
|
| if self.name:
|
| # Named namespace
|
| if not Match((r'};*\s*(//|/\*).*\bnamespace\s+' + re.escape(self.name) +
|
| @@ -1532,7 +1842,6 @@ class _NestingState(object):
|
| #else
|
| struct ResultDetailsPageElementExtensionPoint : public Extension {
|
| #endif
|
| - (see http://go/qwddn for original example)
|
|
|
| We make the following assumptions (good enough for most files):
|
| - Preprocessor condition evaluates to true from #if up to first
|
| @@ -1660,8 +1969,8 @@ class _NestingState(object):
|
| # To avoid these cases, we ignore classes that are followed by '=' or '>'
|
| class_decl_match = Match(
|
| r'\s*(template\s*<[\w\s<>,:]*>\s*)?'
|
| - '(class|struct)\s+([A-Z_]+\s+)*(\w+(?:::\w+)*)'
|
| - '(([^=>]|<[^<>]*>)*)$', line)
|
| + r'(class|struct)\s+([A-Z_]+\s+)*(\w+(?:::\w+)*)'
|
| + r'(([^=>]|<[^<>]*>|<[^<>]*<[^<>]*>\s*>)*)$', line)
|
| if (class_decl_match and
|
| (not self.stack or self.stack[-1].open_parentheses == 0)):
|
| self.stack.append(_ClassInfo(
|
| @@ -1676,9 +1985,29 @@ class _NestingState(object):
|
|
|
| # Update access control if we are inside a class/struct
|
| if self.stack and isinstance(self.stack[-1], _ClassInfo):
|
| - access_match = Match(r'\s*(public|private|protected)\s*:', line)
|
| + classinfo = self.stack[-1]
|
| + access_match = Match(
|
| + r'^(.*)\b(public|private|protected|signals)(\s+(?:slots\s*)?)?'
|
| + r':(?:[^:]|$)',
|
| + line)
|
| if access_match:
|
| - self.stack[-1].access = access_match.group(1)
|
| + classinfo.access = access_match.group(2)
|
| +
|
| + # Check that access keywords are indented +1 space. Skip this
|
| + # check if the keywords are not preceded by whitespaces.
|
| + indent = access_match.group(1)
|
| + if (len(indent) != classinfo.class_indent + 1 and
|
| + Match(r'^\s*$', indent)):
|
| + if classinfo.is_struct:
|
| + parent = 'struct ' + classinfo.name
|
| + else:
|
| + parent = 'class ' + classinfo.name
|
| + slots = ''
|
| + if access_match.group(3):
|
| + slots = access_match.group(3)
|
| + error(filename, linenum, 'whitespace/indent', 3,
|
| + '%s%s: should be indented +1 space inside %s' % (
|
| + access_match.group(2), slots, parent))
|
|
|
| # Consume braces or semicolons from what's left of the line
|
| while True:
|
| @@ -1728,8 +2057,8 @@ class _NestingState(object):
|
| return classinfo
|
| return None
|
|
|
| - def CheckClassFinished(self, filename, error):
|
| - """Checks that all classes have been completely parsed.
|
| + def CheckCompletedBlocks(self, filename, error):
|
| + """Checks that all classes and namespaces have been completely parsed.
|
|
|
| Call this when all lines in a file have been processed.
|
| Args:
|
| @@ -1744,11 +2073,15 @@ class _NestingState(object):
|
| error(filename, obj.starting_linenum, 'build/class', 5,
|
| 'Failed to find complete declaration of class %s' %
|
| obj.name)
|
| + elif isinstance(obj, _NamespaceInfo):
|
| + error(filename, obj.starting_linenum, 'build/namespaces', 5,
|
| + 'Failed to find complete declaration of namespace %s' %
|
| + obj.name)
|
|
|
|
|
| def CheckForNonStandardConstructs(filename, clean_lines, linenum,
|
| nesting_state, error):
|
| - """Logs an error if we see certain non-ANSI constructs ignored by gcc-2.
|
| + r"""Logs an error if we see certain non-ANSI constructs ignored by gcc-2.
|
|
|
| Complain about several constructs which gcc-2 accepts, but which are
|
| not standard C++. Warning about these in lint is one way to ease the
|
| @@ -1847,8 +2180,8 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum,
|
| line)
|
| if (args and
|
| args.group(1) != 'void' and
|
| - not Match(r'(const\s+)?%s\s*(?:<\w+>\s*)?&' % re.escape(base_classname),
|
| - args.group(1).strip())):
|
| + not Match(r'(const\s+)?%s(\s+const)?\s*(?:<\w+>\s*)?&'
|
| + % re.escape(base_classname), args.group(1).strip())):
|
| error(filename, linenum, 'runtime/explicit', 5,
|
| 'Single-argument constructors should be marked explicit.')
|
|
|
| @@ -1891,7 +2224,8 @@ def CheckSpacingForFunctionCall(filename, line, linenum, error):
|
| # Note that we assume the contents of [] to be short enough that
|
| # they'll never need to wrap.
|
| if ( # Ignore control structures.
|
| - not Search(r'\b(if|for|while|switch|return|delete)\b', fncall) and
|
| + not Search(r'\b(if|for|while|switch|return|new|delete|catch|sizeof)\b',
|
| + fncall) and
|
| # Ignore pointers/references to functions.
|
| not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and
|
| # Ignore pointers/references to arrays.
|
| @@ -1904,7 +2238,7 @@ def CheckSpacingForFunctionCall(filename, line, linenum, error):
|
| 'Extra space after (')
|
| if (Search(r'\w\s+\(', fncall) and
|
| not Search(r'#\s*define|typedef', fncall) and
|
| - not Search(r'\w\s+\((\w+::)?\*\w+\)\(', fncall)):
|
| + not Search(r'\w\s+\((\w+::)*\*\w+\)\(', fncall)):
|
| error(filename, linenum, 'whitespace/parens', 4,
|
| 'Extra space before ( in function call')
|
| # If the ) is followed only by a newline or a { + newline, assume it's
|
| @@ -2032,7 +2366,7 @@ def CheckComment(comment, filename, linenum, error):
|
| '"// TODO(my_username): Stuff."')
|
|
|
| middle_whitespace = match.group(3)
|
| - # Comparisons made explicit for correctness -- pylint: disable-msg=C6403
|
| + # Comparisons made explicit for correctness -- pylint: disable=g-explicit-bool-comparison
|
| if middle_whitespace != ' ' and middle_whitespace != '':
|
| error(filename, linenum, 'whitespace/todo', 2,
|
| 'TODO(my_username) should be followed by a space')
|
| @@ -2089,8 +2423,7 @@ def FindNextMatchingAngleBracket(clean_lines, linenum, init_suffix):
|
| # We could also check all other operators and terminate the search
|
| # early, e.g. if we got something like this "a<b+c", the "<" is
|
| # most likely a less-than operator, but then we will get false
|
| - # positives for default arguments (e.g. http://go/prccd) and
|
| - # other template expressions (e.g. http://go/oxcjq).
|
| + # positives for default arguments and other template expressions.
|
| match = Search(r'^[^<>(),;\[\]]*([<>(),;\[\]])(.*)$', line)
|
| if match:
|
| # Found an operator, update nesting stack
|
| @@ -2213,7 +2546,10 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
|
| error: The function to call with any errors found.
|
| """
|
|
|
| - raw = clean_lines.raw_lines
|
| + # Don't use "elided" lines here, otherwise we can't check commented lines.
|
| + # Don't want to use "raw" either, because we don't want to check inside C++11
|
| + # raw strings,
|
| + raw = clean_lines.lines_without_raw_strings
|
| line = raw[linenum]
|
|
|
| # Before nixing comments, check if the line is blank for no good
|
| @@ -2267,7 +2603,8 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
|
|
|
| if not exception:
|
| error(filename, linenum, 'whitespace/blank_line', 2,
|
| - 'Blank line at the start of a code block. Is this needed?')
|
| + 'Redundant blank line at the start of a code block '
|
| + 'should be deleted.')
|
| # Ignore blank lines at the end of a block in a long if-else
|
| # chain, like this:
|
| # if (condition1) {
|
| @@ -2282,7 +2619,8 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
|
| and Match(r'\s*}', next_line)
|
| and next_line.find('} else ') == -1):
|
| error(filename, linenum, 'whitespace/blank_line', 3,
|
| - 'Blank line at the end of a code block. Is this needed?')
|
| + 'Redundant blank line at the end of a code block '
|
| + 'should be deleted.')
|
|
|
| matched = Match(r'\s*(public|protected|private):', prev_line)
|
| if matched:
|
| @@ -2293,7 +2631,7 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
|
| commentpos = line.find('//')
|
| if commentpos != -1:
|
| # Check if the // may be in quotes. If so, ignore it
|
| - # Comparisons made explicit for clarity -- pylint: disable-msg=C6403
|
| + # Comparisons made explicit for clarity -- pylint: disable=g-explicit-bool-comparison
|
| if (line.count('"', 0, commentpos) -
|
| line.count('\\"', 0, commentpos)) % 2 == 0: # not in quotes
|
| # Allow one space for new scopes, two spaces otherwise:
|
| @@ -2312,10 +2650,15 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
|
| # //----------------------------------------------------------
|
| # or are an empty C++ style Doxygen comment, like:
|
| # ///
|
| + # or C++ style Doxygen comments placed after the variable:
|
| + # ///< Header comment
|
| + # //!< Header comment
|
| # or they begin with multiple slashes followed by a space:
|
| # //////// Header comment
|
| match = (Search(r'[=/-]{4,}\s*$', line[commentend:]) or
|
| Search(r'^/$', line[commentend:]) or
|
| + Search(r'^!< ', line[commentend:]) or
|
| + Search(r'^/< ', line[commentend:]) or
|
| Search(r'^/+ ', line[commentend:]))
|
| if not match:
|
| error(filename, linenum, 'whitespace/comments', 4,
|
| @@ -2349,8 +2692,11 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
|
| 'Missing spaces around %s' % match.group(1))
|
| # We allow no-spaces around << when used like this: 10<<20, but
|
| # not otherwise (particularly, not when used as streams)
|
| - match = Search(r'(\S)(?:L|UL|ULL|l|ul|ull)?<<(\S)', line)
|
| - if match and not (match.group(1).isdigit() and match.group(2).isdigit()):
|
| + # Also ignore using ns::operator<<;
|
| + match = Search(r'(operator|\S)(?:L|UL|ULL|l|ul|ull)?<<(\S)', line)
|
| + if (match and
|
| + not (match.group(1).isdigit() and match.group(2).isdigit()) and
|
| + not (match.group(1) == 'operator' and match.group(2) == ';')):
|
| error(filename, linenum, 'whitespace/operators', 3,
|
| 'Missing spaces around <<')
|
| elif not Match(r'#.*include', line):
|
| @@ -2421,13 +2767,22 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
|
| not match.group(2) and Search(r'\bfor\s*\(.*; \)', line)):
|
| error(filename, linenum, 'whitespace/parens', 5,
|
| 'Mismatching spaces inside () in %s' % match.group(1))
|
| - if not len(match.group(2)) in [0, 1]:
|
| + if len(match.group(2)) not in [0, 1]:
|
| error(filename, linenum, 'whitespace/parens', 5,
|
| 'Should have zero or one spaces inside ( and ) in %s' %
|
| match.group(1))
|
|
|
| # You should always have a space after a comma (either as fn arg or operator)
|
| - if Search(r',[^\s]', line):
|
| + #
|
| + # This does not apply when the non-space character following the
|
| + # comma is another comma, since the only time when that happens is
|
| + # for empty macro arguments.
|
| + #
|
| + # We run this check in two passes: first pass on elided lines to
|
| + # verify that lines contain missing whitespaces, second pass on raw
|
| + # lines to confirm that those missing whitespaces are not due to
|
| + # elided comments.
|
| + if Search(r',[^,\s]', line) and Search(r',[^,\s]', raw[linenum]):
|
| error(filename, linenum, 'whitespace/comma', 3,
|
| 'Missing space after ,')
|
|
|
| @@ -2446,9 +2801,45 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
|
| # an initializer list, for instance), you should have spaces before your
|
| # braces. And since you should never have braces at the beginning of a line,
|
| # this is an easy test.
|
| - if Search(r'[^ ({]{', line):
|
| - error(filename, linenum, 'whitespace/braces', 5,
|
| - 'Missing space before {')
|
| + match = Match(r'^(.*[^ ({]){', line)
|
| + if match:
|
| + # Try a bit harder to check for brace initialization. This
|
| + # happens in one of the following forms:
|
| + # Constructor() : initializer_list_{} { ... }
|
| + # Constructor{}.MemberFunction()
|
| + # Type variable{};
|
| + # FunctionCall(type{}, ...);
|
| + # LastArgument(..., type{});
|
| + # LOG(INFO) << type{} << " ...";
|
| + # map_of_type[{...}] = ...;
|
| + #
|
| + # We check for the character following the closing brace, and
|
| + # silence the warning if it's one of those listed above, i.e.
|
| + # "{.;,)<]".
|
| + #
|
| + # To account for nested initializer list, we allow any number of
|
| + # closing braces up to "{;,)<". We can't simply silence the
|
| + # warning on first sight of closing brace, because that would
|
| + # cause false negatives for things that are not initializer lists.
|
| + # Silence this: But not this:
|
| + # Outer{ if (...) {
|
| + # Inner{...} if (...){ // Missing space before {
|
| + # }; }
|
| + #
|
| + # There is a false negative with this approach if people inserted
|
| + # spurious semicolons, e.g. "if (cond){};", but we will catch the
|
| + # spurious semicolon with a separate check.
|
| + (endline, endlinenum, endpos) = CloseExpression(
|
| + clean_lines, linenum, len(match.group(1)))
|
| + trailing_text = ''
|
| + if endpos > -1:
|
| + trailing_text = endline[endpos:]
|
| + for offset in xrange(endlinenum + 1,
|
| + min(endlinenum + 3, clean_lines.NumLines() - 1)):
|
| + trailing_text += clean_lines.elided[offset]
|
| + if not Match(r'^[\s}]*[{.;,)<\]]', trailing_text):
|
| + error(filename, linenum, 'whitespace/braces', 5,
|
| + 'Missing space before {')
|
|
|
| # Make sure '} else {' has spaces.
|
| if Search(r'}else', line):
|
| @@ -2576,15 +2967,15 @@ def CheckBraces(filename, clean_lines, linenum, error):
|
| line = clean_lines.elided[linenum] # get rid of comments and strings
|
|
|
| if Match(r'\s*{\s*$', line):
|
| - # We allow an open brace to start a line in the case where someone
|
| - # is using braces in a block to explicitly create a new scope,
|
| - # which is commonly used to control the lifetime of
|
| - # stack-allocated variables. We don't detect this perfectly: we
|
| - # just don't complain if the last non-whitespace character on the
|
| - # previous non-blank line is ';', ':', '{', or '}', or if the previous
|
| - # line starts a preprocessor block.
|
| + # We allow an open brace to start a line in the case where someone is using
|
| + # braces in a block to explicitly create a new scope, which is commonly used
|
| + # to control the lifetime of stack-allocated variables. Braces are also
|
| + # used for brace initializers inside function calls. We don't detect this
|
| + # perfectly: we just don't complain if the last non-whitespace character on
|
| + # the previous non-blank line is ',', ';', ':', '(', '{', or '}', or if the
|
| + # previous line starts a preprocessor block.
|
| prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0]
|
| - if (not Search(r'[;:}{]\s*$', prevline) and
|
| + if (not Search(r'[,;:}{(]\s*$', prevline) and
|
| not Match(r'\s*#', prevline)):
|
| error(filename, linenum, 'whitespace/braces', 4,
|
| '{ should almost always be at the end of the previous line')
|
| @@ -2622,25 +3013,123 @@ def CheckBraces(filename, clean_lines, linenum, error):
|
| error(filename, linenum, 'whitespace/newline', 4,
|
| 'do/while clauses should not be on a single line')
|
|
|
| - # Braces shouldn't be followed by a ; unless they're defining a struct
|
| - # or initializing an array.
|
| - # We can't tell in general, but we can for some common cases.
|
| - prevlinenum = linenum
|
| - while True:
|
| - (prevline, prevlinenum) = GetPreviousNonBlankLine(clean_lines, prevlinenum)
|
| - if Match(r'\s+{.*}\s*;', line) and not prevline.count(';'):
|
| - line = prevline + line
|
| - else:
|
| - break
|
| - if (Search(r'{.*}\s*;', line) and
|
| - line.count('{') == line.count('}') and
|
| - not Search(r'struct|class|enum|\s*=\s*{', line)):
|
| - error(filename, linenum, 'readability/braces', 4,
|
| - "You don't need a ; after a }")
|
| + # Block bodies should not be followed by a semicolon. Due to C++11
|
| + # brace initialization, there are more places where semicolons are
|
| + # required than not, so we use a whitelist approach to check these
|
| + # rather than a blacklist. These are the places where "};" should
|
| + # be replaced by just "}":
|
| + # 1. Some flavor of block following closing parenthesis:
|
| + # for (;;) {};
|
| + # while (...) {};
|
| + # switch (...) {};
|
| + # Function(...) {};
|
| + # if (...) {};
|
| + # if (...) else if (...) {};
|
| + #
|
| + # 2. else block:
|
| + # if (...) else {};
|
| + #
|
| + # 3. const member function:
|
| + # Function(...) const {};
|
| + #
|
| + # 4. Block following some statement:
|
| + # x = 42;
|
| + # {};
|
| + #
|
| + # 5. Block at the beginning of a function:
|
| + # Function(...) {
|
| + # {};
|
| + # }
|
| + #
|
| + # Note that naively checking for the preceding "{" will also match
|
| + # braces inside multi-dimensional arrays, but this is fine since
|
| + # that expression will not contain semicolons.
|
| + #
|
| + # 6. Block following another block:
|
| + # while (true) {}
|
| + # {};
|
| + #
|
| + # 7. End of namespaces:
|
| + # namespace {};
|
| + #
|
| + # These semicolons seems far more common than other kinds of
|
| + # redundant semicolons, possibly due to people converting classes
|
| + # to namespaces. For now we do not warn for this case.
|
| + #
|
| + # Try matching case 1 first.
|
| + match = Match(r'^(.*\)\s*)\{', line)
|
| + if match:
|
| + # Matched closing parenthesis (case 1). Check the token before the
|
| + # matching opening parenthesis, and don't warn if it looks like a
|
| + # macro. This avoids these false positives:
|
| + # - macro that defines a base class
|
| + # - multi-line macro that defines a base class
|
| + # - macro that defines the whole class-head
|
| + #
|
| + # But we still issue warnings for macros that we know are safe to
|
| + # warn, specifically:
|
| + # - TEST, TEST_F, TEST_P, MATCHER, MATCHER_P
|
| + # - TYPED_TEST
|
| + # - INTERFACE_DEF
|
| + # - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED:
|
| + #
|
| + # We implement a whitelist of safe macros instead of a blacklist of
|
| + # unsafe macros, even though the latter appears less frequently in
|
| + # google code and would have been easier to implement. This is because
|
| + # the downside for getting the whitelist wrong means some extra
|
| + # semicolons, while the downside for getting the blacklist wrong
|
| + # would result in compile errors.
|
| + #
|
| + # In addition to macros, we also don't want to warn on compound
|
| + # literals.
|
| + closing_brace_pos = match.group(1).rfind(')')
|
| + opening_parenthesis = ReverseCloseExpression(
|
| + clean_lines, linenum, closing_brace_pos)
|
| + if opening_parenthesis[2] > -1:
|
| + line_prefix = opening_parenthesis[0][0:opening_parenthesis[2]]
|
| + macro = Search(r'\b([A-Z_]+)\s*$', line_prefix)
|
| + if ((macro and
|
| + macro.group(1) not in (
|
| + 'TEST', 'TEST_F', 'MATCHER', 'MATCHER_P', 'TYPED_TEST',
|
| + 'EXCLUSIVE_LOCKS_REQUIRED', 'SHARED_LOCKS_REQUIRED',
|
| + 'LOCKS_EXCLUDED', 'INTERFACE_DEF')) or
|
| + Search(r'\s+=\s*$', line_prefix)):
|
| + match = None
|
| +
|
| + else:
|
| + # Try matching cases 2-3.
|
| + match = Match(r'^(.*(?:else|\)\s*const)\s*)\{', line)
|
| + if not match:
|
| + # Try matching cases 4-6. These are always matched on separate lines.
|
| + #
|
| + # Note that we can't simply concatenate the previous line to the
|
| + # current line and do a single match, otherwise we may output
|
| + # duplicate warnings for the blank line case:
|
| + # if (cond) {
|
| + # // blank line
|
| + # }
|
| + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0]
|
| + if prevline and Search(r'[;{}]\s*$', prevline):
|
| + match = Match(r'^(\s*)\{', line)
|
| +
|
| + # Check matching closing brace
|
| + if match:
|
| + (endline, endlinenum, endpos) = CloseExpression(
|
| + clean_lines, linenum, len(match.group(1)))
|
| + if endpos > -1 and Match(r'^\s*;', endline[endpos:]):
|
| + # Current {} pair is eligible for semicolon check, and we have found
|
| + # the redundant semicolon, output warning here.
|
| + #
|
| + # Note: because we are scanning forward for opening braces, and
|
| + # outputting warnings for the matching closing brace, if there are
|
| + # nested blocks with trailing semicolons, we will get the error
|
| + # messages in reversed order.
|
| + error(filename, endlinenum, 'readability/braces', 4,
|
| + "You don't need a ; after a }")
|
|
|
|
|
| -def CheckEmptyLoopBody(filename, clean_lines, linenum, error):
|
| - """Loop for empty loop body with only a single semicolon.
|
| +def CheckEmptyBlockBody(filename, clean_lines, linenum, error):
|
| + """Look for empty loop/conditional body with only a single semicolon.
|
|
|
| Args:
|
| filename: The name of the current file.
|
| @@ -2652,8 +3141,12 @@ def CheckEmptyLoopBody(filename, clean_lines, linenum, error):
|
| # Search for loop keywords at the beginning of the line. Because only
|
| # whitespaces are allowed before the keywords, this will also ignore most
|
| # do-while-loops, since those lines should start with closing brace.
|
| + #
|
| + # We also check "if" blocks here, since an empty conditional block
|
| + # is likely an error.
|
| line = clean_lines.elided[linenum]
|
| - if Match(r'\s*(for|while)\s*\(', line):
|
| + matched = Match(r'\s*(for|while|if)\s*\(', line)
|
| + if matched:
|
| # Find the end of the conditional expression
|
| (end_line, end_linenum, end_pos) = CloseExpression(
|
| clean_lines, linenum, line.find('('))
|
| @@ -2662,43 +3155,12 @@ def CheckEmptyLoopBody(filename, clean_lines, linenum, error):
|
| # No warning for all other cases, including whitespace or newline, since we
|
| # have a separate check for semicolons preceded by whitespace.
|
| if end_pos >= 0 and Match(r';', end_line[end_pos:]):
|
| - error(filename, end_linenum, 'whitespace/empty_loop_body', 5,
|
| - 'Empty loop bodies should use {} or continue')
|
| -
|
| -
|
| -def ReplaceableCheck(operator, macro, line):
|
| - """Determine whether a basic CHECK can be replaced with a more specific one.
|
| -
|
| - For example suggest using CHECK_EQ instead of CHECK(a == b) and
|
| - similarly for CHECK_GE, CHECK_GT, CHECK_LE, CHECK_LT, CHECK_NE.
|
| -
|
| - Args:
|
| - operator: The C++ operator used in the CHECK.
|
| - macro: The CHECK or EXPECT macro being called.
|
| - line: The current source line.
|
| -
|
| - Returns:
|
| - True if the CHECK can be replaced with a more specific one.
|
| - """
|
| -
|
| - # This matches decimal and hex integers, strings, and chars (in that order).
|
| - match_constant = r'([-+]?(\d+|0[xX][0-9a-fA-F]+)[lLuU]{0,3}|".*"|\'.*\')'
|
| -
|
| - # Expression to match two sides of the operator with something that
|
| - # looks like a literal, since CHECK(x == iterator) won't compile.
|
| - # This means we can't catch all the cases where a more specific
|
| - # CHECK is possible, but it's less annoying than dealing with
|
| - # extraneous warnings.
|
| - match_this = (r'\s*' + macro + r'\((\s*' +
|
| - match_constant + r'\s*' + operator + r'[^<>].*|'
|
| - r'.*[^<>]' + operator + r'\s*' + match_constant +
|
| - r'\s*\))')
|
| -
|
| - # Don't complain about CHECK(x == NULL) or similar because
|
| - # CHECK_EQ(x, NULL) won't compile (requires a cast).
|
| - # Also, don't complain about more complex boolean expressions
|
| - # involving && or || such as CHECK(a == b || c == d).
|
| - return Match(match_this, line) and not Search(r'NULL|&&|\|\|', line)
|
| + if matched.group(1) == 'if':
|
| + error(filename, end_linenum, 'whitespace/empty_conditional_body', 5,
|
| + 'Empty conditional bodies should use {}')
|
| + else:
|
| + error(filename, end_linenum, 'whitespace/empty_loop_body', 5,
|
| + 'Empty loop bodies should use {} or continue')
|
|
|
|
|
| def CheckCheck(filename, clean_lines, linenum, error):
|
| @@ -2712,26 +3174,120 @@ def CheckCheck(filename, clean_lines, linenum, error):
|
| """
|
|
|
| # Decide the set of replacement macros that should be suggested
|
| - raw_lines = clean_lines.raw_lines
|
| - current_macro = ''
|
| + lines = clean_lines.elided
|
| + check_macro = None
|
| + start_pos = -1
|
| for macro in _CHECK_MACROS:
|
| - if raw_lines[linenum].find(macro) >= 0:
|
| - current_macro = macro
|
| + i = lines[linenum].find(macro)
|
| + if i >= 0:
|
| + check_macro = macro
|
| +
|
| + # Find opening parenthesis. Do a regular expression match here
|
| + # to make sure that we are matching the expected CHECK macro, as
|
| + # opposed to some other macro that happens to contain the CHECK
|
| + # substring.
|
| + matched = Match(r'^(.*\b' + check_macro + r'\s*)\(', lines[linenum])
|
| + if not matched:
|
| + continue
|
| + start_pos = len(matched.group(1))
|
| break
|
| - if not current_macro:
|
| + if not check_macro or start_pos < 0:
|
| # Don't waste time here if line doesn't contain 'CHECK' or 'EXPECT'
|
| return
|
|
|
| - line = clean_lines.elided[linenum] # get rid of comments and strings
|
| + # Find end of the boolean expression by matching parentheses
|
| + (last_line, end_line, end_pos) = CloseExpression(
|
| + clean_lines, linenum, start_pos)
|
| + if end_pos < 0:
|
| + return
|
| + if linenum == end_line:
|
| + expression = lines[linenum][start_pos + 1:end_pos - 1]
|
| + else:
|
| + expression = lines[linenum][start_pos + 1:]
|
| + for i in xrange(linenum + 1, end_line):
|
| + expression += lines[i]
|
| + expression += last_line[0:end_pos - 1]
|
| +
|
| + # Parse expression so that we can take parentheses into account.
|
| + # This avoids false positives for inputs like "CHECK((a < 4) == b)",
|
| + # which is not replaceable by CHECK_LE.
|
| + lhs = ''
|
| + rhs = ''
|
| + operator = None
|
| + while expression:
|
| + matched = Match(r'^\s*(<<|<<=|>>|>>=|->\*|->|&&|\|\||'
|
| + r'==|!=|>=|>|<=|<|\()(.*)$', expression)
|
| + if matched:
|
| + token = matched.group(1)
|
| + if token == '(':
|
| + # Parenthesized operand
|
| + expression = matched.group(2)
|
| + (end, _) = FindEndOfExpressionInLine(expression, 0, 1, '(', ')')
|
| + if end < 0:
|
| + return # Unmatched parenthesis
|
| + lhs += '(' + expression[0:end]
|
| + expression = expression[end:]
|
| + elif token in ('&&', '||'):
|
| + # Logical and/or operators. This means the expression
|
| + # contains more than one term, for example:
|
| + # CHECK(42 < a && a < b);
|
| + #
|
| + # These are not replaceable with CHECK_LE, so bail out early.
|
| + return
|
| + elif token in ('<<', '<<=', '>>', '>>=', '->*', '->'):
|
| + # Non-relational operator
|
| + lhs += token
|
| + expression = matched.group(2)
|
| + else:
|
| + # Relational operator
|
| + operator = token
|
| + rhs = matched.group(2)
|
| + break
|
| + else:
|
| + # Unparenthesized operand. Instead of appending to lhs one character
|
| + # at a time, we do another regular expression match to consume several
|
| + # characters at once if possible. Trivial benchmark shows that this
|
| + # is more efficient when the operands are longer than a single
|
| + # character, which is generally the case.
|
| + matched = Match(r'^([^-=!<>()&|]+)(.*)$', expression)
|
| + if not matched:
|
| + matched = Match(r'^(\s*\S)(.*)$', expression)
|
| + if not matched:
|
| + break
|
| + lhs += matched.group(1)
|
| + expression = matched.group(2)
|
|
|
| - # Encourage replacing plain CHECKs with CHECK_EQ/CHECK_NE/etc.
|
| - for operator in ['==', '!=', '>=', '>', '<=', '<']:
|
| - if ReplaceableCheck(operator, current_macro, line):
|
| - error(filename, linenum, 'readability/check', 2,
|
| - 'Consider using %s instead of %s(a %s b)' % (
|
| - _CHECK_REPLACEMENT[current_macro][operator],
|
| - current_macro, operator))
|
| - break
|
| + # Only apply checks if we got all parts of the boolean expression
|
| + if not (lhs and operator and rhs):
|
| + return
|
| +
|
| + # Check that rhs do not contain logical operators. We already know
|
| + # that lhs is fine since the loop above parses out && and ||.
|
| + if rhs.find('&&') > -1 or rhs.find('||') > -1:
|
| + return
|
| +
|
| + # At least one of the operands must be a constant literal. This is
|
| + # to avoid suggesting replacements for unprintable things like
|
| + # CHECK(variable != iterator)
|
| + #
|
| + # The following pattern matches decimal, hex integers, strings, and
|
| + # characters (in that order).
|
| + lhs = lhs.strip()
|
| + rhs = rhs.strip()
|
| + match_constant = r'^([-+]?(\d+|0[xX][0-9a-fA-F]+)[lLuU]{0,3}|".*"|\'.*\')$'
|
| + if Match(match_constant, lhs) or Match(match_constant, rhs):
|
| + # Note: since we know both lhs and rhs, we can provide a more
|
| + # descriptive error message like:
|
| + # Consider using CHECK_EQ(x, 42) instead of CHECK(x == 42)
|
| + # Instead of:
|
| + # Consider using CHECK_EQ instead of CHECK(a == b)
|
| + #
|
| + # We are still keeping the less descriptive message because if lhs
|
| + # or rhs gets long, the error message might become unreadable.
|
| + error(filename, linenum, 'readability/check', 2,
|
| + 'Consider using %s instead of %s(a %s b)' % (
|
| + _CHECK_REPLACEMENT[check_macro][operator],
|
| + check_macro, operator))
|
|
|
|
|
| def CheckAltTokens(filename, clean_lines, linenum, error):
|
| @@ -2806,7 +3362,10 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state,
|
| error: The function to call with any errors found.
|
| """
|
|
|
| - raw_lines = clean_lines.raw_lines
|
| + # Don't use "elided" lines here, otherwise we can't check commented lines.
|
| + # Don't want to use "raw" either, because we don't want to check inside C++11
|
| + # raw strings,
|
| + raw_lines = clean_lines.lines_without_raw_strings
|
| line = raw_lines[linenum]
|
|
|
| if line.find('\t') != -1:
|
| @@ -2832,21 +3391,12 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state,
|
| if line and line[-1].isspace():
|
| error(filename, linenum, 'whitespace/end_of_line', 4,
|
| 'Line ends in whitespace. Consider deleting these extra spaces.')
|
| - # There are certain situations we allow one space, notably for labels
|
| + # There are certain situations we allow one space, notably for section labels
|
| elif ((initial_spaces == 1 or initial_spaces == 3) and
|
| not Match(r'\s*\w+\s*:\s*$', cleansed_line)):
|
| error(filename, linenum, 'whitespace/indent', 3,
|
| 'Weird number of spaces at line-start. '
|
| 'Are you using a 2-space indent?')
|
| - # Labels should always be indented at least one space.
|
| - elif not initial_spaces and line[:2] != '//' and Search(r'[^:]:\s*$',
|
| - line):
|
| - error(filename, linenum, 'whitespace/labels', 4,
|
| - 'Labels should always be indented at least one space. '
|
| - 'If this is a member-initializer list in a constructor or '
|
| - 'the base class list in a class definition, the colon should '
|
| - 'be on the following line.')
|
| -
|
|
|
| # Check if the line is a header guard.
|
| is_header_guard = False
|
| @@ -2868,12 +3418,14 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state,
|
| not Match(r'^\s*//.*http(s?)://\S*$', line) and
|
| not Match(r'^// \$Id:.*#[0-9]+ \$$', line)):
|
| line_width = GetLineWidth(line)
|
| - if line_width > 100:
|
| + extended_length = int((_line_length * 1.25))
|
| + if line_width > extended_length:
|
| error(filename, linenum, 'whitespace/line_length', 4,
|
| - 'Lines should very rarely be longer than 100 characters')
|
| - elif line_width > 80:
|
| + 'Lines should very rarely be longer than %i characters' %
|
| + extended_length)
|
| + elif line_width > _line_length:
|
| error(filename, linenum, 'whitespace/line_length', 2,
|
| - 'Lines should be <= 80 characters long')
|
| + 'Lines should be <= %i characters long' % _line_length)
|
|
|
| if (cleansed_line.count(';') > 1 and
|
| # for loops are allowed two ;'s (and may run over two lines).
|
| @@ -2889,7 +3441,7 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state,
|
|
|
| # Some more style checks
|
| CheckBraces(filename, clean_lines, linenum, error)
|
| - CheckEmptyLoopBody(filename, clean_lines, linenum, error)
|
| + CheckEmptyBlockBody(filename, clean_lines, linenum, error)
|
| CheckAccess(filename, clean_lines, linenum, nesting_state, error)
|
| CheckSpacing(filename, clean_lines, linenum, nesting_state, error)
|
| CheckCheck(filename, clean_lines, linenum, error)
|
| @@ -2979,8 +3531,7 @@ def _ClassifyInclude(fileinfo, include, is_system):
|
| """
|
| # This is a list of all standard c++ header files, except
|
| # those already checked for above.
|
| - is_stl_h = include in _STL_HEADERS
|
| - is_cpp_h = is_stl_h or include in _CPP_HEADERS
|
| + is_cpp_h = include in _CPP_HEADERS
|
|
|
| if is_system:
|
| if is_cpp_h:
|
| @@ -3068,9 +3619,12 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error):
|
| error(filename, linenum, 'build/include_order', 4,
|
| '%s. Should be: %s.h, c system, c++ system, other.' %
|
| (error_message, fileinfo.BaseName()))
|
| - if not include_state.IsInAlphabeticalOrder(include):
|
| + canonical_include = include_state.CanonicalizeAlphabeticalOrder(include)
|
| + if not include_state.IsInAlphabeticalOrder(
|
| + clean_lines, linenum, canonical_include):
|
| error(filename, linenum, 'build/include_alpha', 4,
|
| 'Include "%s" not in alphabetical order' % include)
|
| + include_state.SetLastHeader(canonical_include)
|
|
|
| # Look for any of the stream classes that are part of standard C++.
|
| match = _RE_PATTERN_INCLUDE.match(line)
|
| @@ -3084,7 +3638,7 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error):
|
|
|
|
|
| def _GetTextInside(text, start_pattern):
|
| - """Retrieves all the text between matching open and close parentheses.
|
| + r"""Retrieves all the text between matching open and close parentheses.
|
|
|
| Given a string of lines and a regular expression string, retrieve all the text
|
| following the expression and between opening punctuation symbols like
|
| @@ -3139,8 +3693,34 @@ def _GetTextInside(text, start_pattern):
|
| return text[start_position:position - 1]
|
|
|
|
|
| -def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
|
| - error):
|
| +# Patterns for matching call-by-reference parameters.
|
| +#
|
| +# Supports nested templates up to 2 levels deep using this messy pattern:
|
| +# < (?: < (?: < [^<>]*
|
| +# >
|
| +# | [^<>] )*
|
| +# >
|
| +# | [^<>] )*
|
| +# >
|
| +_RE_PATTERN_IDENT = r'[_a-zA-Z]\w*' # =~ [[:alpha:]][[:alnum:]]*
|
| +_RE_PATTERN_TYPE = (
|
| + r'(?:const\s+)?(?:typename\s+|class\s+|struct\s+|union\s+|enum\s+)?'
|
| + r'(?:\w|'
|
| + r'\s*<(?:<(?:<[^<>]*>|[^<>])*>|[^<>])*>|'
|
| + r'::)+')
|
| +# A call-by-reference parameter ends with '& identifier'.
|
| +_RE_PATTERN_REF_PARAM = re.compile(
|
| + r'(' + _RE_PATTERN_TYPE + r'(?:\s*(?:\bconst\b|[*]))*\s*'
|
| + r'&\s*' + _RE_PATTERN_IDENT + r')\s*(?:=[^,()]+)?[,)]')
|
| +# A call-by-const-reference parameter either ends with 'const& identifier'
|
| +# or looks like 'const type& identifier' when 'type' is atomic.
|
| +_RE_PATTERN_CONST_REF_PARAM = (
|
| + r'(?:.*\s*\bconst\s*&\s*' + _RE_PATTERN_IDENT +
|
| + r'|const\s+' + _RE_PATTERN_TYPE + r'\s*&\s*' + _RE_PATTERN_IDENT + r')')
|
| +
|
| +
|
| +def CheckLanguage(filename, clean_lines, linenum, file_extension,
|
| + include_state, nesting_state, error):
|
| """Checks rules from the 'C++ language rules' section of cppguide.html.
|
|
|
| Some of these rules are hard to test (function overloading, using
|
| @@ -3152,6 +3732,8 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
|
| 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.
|
| + nesting_state: A _NestingState instance which maintains information about
|
| + the current stack of nested blocks being parsed.
|
| error: The function to call with any errors found.
|
| """
|
| # If the line is empty or consists of entirely a comment, no need to
|
| @@ -3165,73 +3747,64 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
|
| CheckIncludeLine(filename, clean_lines, linenum, include_state, error)
|
| return
|
|
|
| - # 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
|
| - # line.
|
| - if linenum + 1 < clean_lines.NumLines():
|
| - extended_line = line + clean_lines.elided[linenum + 1]
|
| - else:
|
| - extended_line = line
|
| + # Reset include state across preprocessor directives. This is meant
|
| + # to silence warnings for conditional includes.
|
| + if Match(r'^\s*#\s*(?:ifdef|elif|else|endif)\b', line):
|
| + include_state.ResetSection()
|
|
|
| # Make Windows paths like Unix.
|
| fullname = os.path.abspath(filename).replace('\\', '/')
|
|
|
| # TODO(unknown): figure out if they're using default arguments in fn proto.
|
|
|
| - # Check for non-const references in functions. This is tricky because &
|
| - # is also used to take the address of something. We allow <> for templates,
|
| - # (ignoring whatever is between the braces) and : for classes.
|
| - # These are complicated re's. They try to capture the following:
|
| - # paren (for fn-prototype start), typename, &, varname. For the const
|
| - # version, we're willing for const to be before typename or after
|
| - # Don't check the implementation on same line.
|
| - fnline = line.split('{', 1)[0]
|
| - if (len(re.findall(r'\([^()]*\b(?:[\w:]|<[^()]*>)+(\s?&|&\s?)\w+', fnline)) >
|
| - len(re.findall(r'\([^()]*\bconst\s+(?:typename\s+)?(?:struct\s+)?'
|
| - r'(?:[\w:]|<[^()]*>)+(\s?&|&\s?)\w+', fnline)) +
|
| - len(re.findall(r'\([^()]*\b(?:[\w:]|<[^()]*>)+\s+const(\s?&|&\s?)[\w]+',
|
| - fnline))):
|
| -
|
| - # We allow non-const references in a few standard places, like functions
|
| - # called "swap()" or iostream operators like "<<" or ">>". We also filter
|
| - # out for loops, which lint otherwise mistakenly thinks are functions.
|
| - if not Search(
|
| - r'(for|swap|Swap|operator[<>][<>])\s*\(\s*'
|
| - r'(?:(?:typename\s*)?[\w:]|<.*>)+\s*&',
|
| - fnline):
|
| - error(filename, linenum, 'runtime/references', 2,
|
| - 'Is this a non-const reference? '
|
| - 'If so, make const or use a pointer.')
|
| -
|
| # Check to see if they're using an conversion function cast.
|
| # I just try to capture the most common basic types, though there are more.
|
| # Parameterless conversion functions, such as bool(), are allowed as they are
|
| # probably a member operator declaration or default constructor.
|
| match = Search(
|
| r'(\bnew\s+)?\b' # Grab 'new' operator, if it's there
|
| - r'(int|float|double|bool|char|int32|uint32|int64|uint64)\([^)]', line)
|
| + r'(int|float|double|bool|char|int32|uint32|int64|uint64)'
|
| + r'(\([^)].*)', line)
|
| if match:
|
| + matched_new = match.group(1)
|
| + matched_type = match.group(2)
|
| + matched_funcptr = match.group(3)
|
| +
|
| # gMock methods are defined using some variant of MOCK_METHODx(name, type)
|
| # where type may be float(), int(string), etc. Without context they are
|
| # virtually indistinguishable from int(x) casts. Likewise, gMock's
|
| # MockCallback takes a template parameter of the form return_type(arg_type),
|
| # which looks much like the cast we're trying to detect.
|
| - if (match.group(1) is None and # If new operator, then this isn't a cast
|
| + #
|
| + # std::function<> wrapper has a similar problem.
|
| + #
|
| + # Return types for function pointers also look like casts if they
|
| + # don't have an extra space.
|
| + if (matched_new is None and # If new operator, then this isn't a cast
|
| not (Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line) or
|
| - Match(r'^\s*MockCallback<.*>', line))):
|
| + Search(r'\bMockCallback<.*>', line) or
|
| + Search(r'\bstd::function<.*>', line)) and
|
| + not (matched_funcptr and
|
| + Match(r'\((?:[^() ]+::\s*\*\s*)?[^() ]+\)\s*\(',
|
| + matched_funcptr))):
|
| # Try a bit harder to catch gmock lines: the only place where
|
| # something looks like an old-style cast is where we declare the
|
| # return type of the mocked method, and the only time when we
|
| # are missing context is if MOCK_METHOD was split across
|
| - # multiple lines (for example http://go/hrfhr ), so we only need
|
| - # to check the previous line for MOCK_METHOD.
|
| - if (linenum == 0 or
|
| - not Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(\S+,\s*$',
|
| - clean_lines.elided[linenum - 1])):
|
| + # multiple lines. The missing MOCK_METHOD is usually one or two
|
| + # lines back, so scan back one or two lines.
|
| + #
|
| + # It's not possible for gmock macros to appear in the first 2
|
| + # lines, since the class head + section name takes up 2 lines.
|
| + if (linenum < 2 or
|
| + not (Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\((?:\S+,)?\s*$',
|
| + clean_lines.elided[linenum - 1]) or
|
| + Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\(\s*$',
|
| + clean_lines.elided[linenum - 2]))):
|
| error(filename, linenum, 'readability/casting', 4,
|
| 'Using deprecated casting style. '
|
| 'Use static_cast<%s>(...) instead' %
|
| - match.group(2))
|
| + matched_type)
|
|
|
| CheckCStyleCast(filename, linenum, line, clean_lines.raw_lines[linenum],
|
| 'static_cast',
|
| @@ -3252,13 +3825,23 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
|
| # In addition, we look for people taking the address of a cast. This
|
| # is dangerous -- casts can assign to temporaries, so the pointer doesn't
|
| # point where you think.
|
| - if Search(
|
| - r'(&\([^)]+\)[\w(])|(&(static|dynamic|reinterpret)_cast\b)', line):
|
| + match = Search(
|
| + r'(?:&\(([^)]+)\)[\w(])|'
|
| + r'(?:&(static|dynamic|down|reinterpret)_cast\b)', line)
|
| + if match and match.group(1) != '*':
|
| error(filename, linenum, 'runtime/casting', 4,
|
| ('Are you taking an address of a cast? '
|
| 'This is dangerous: could be a temp var. '
|
| 'Take the address before doing the cast, rather than after'))
|
|
|
| + # 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
|
| + # line.
|
| + if linenum + 1 < clean_lines.NumLines():
|
| + extended_line = line + clean_lines.elided[linenum + 1]
|
| + else:
|
| + extended_line = line
|
| +
|
| # Check for people declaring static/global STL strings at the top level.
|
| # This is dangerous because the C++ language does not guarantee that
|
| # globals with constructors are initialized before the first access.
|
| @@ -3268,20 +3851,18 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
|
| # Make sure it's not a function.
|
| # Function template specialization looks like: "string foo<Type>(...".
|
| # Class template definitions look like: "string Foo<Type>::Method(...".
|
| - if match and not Match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)?\s*\(([^"]|$)',
|
| - match.group(3)):
|
| + #
|
| + # Also ignore things that look like operators. These are matched separately
|
| + # because operator names cross non-word boundaries. If we change the pattern
|
| + # above, we would decrease the accuracy of matching identifiers.
|
| + if (match and
|
| + not Search(r'\boperator\W', line) and
|
| + not Match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)?\s*\(([^"]|$)', match.group(3))):
|
| error(filename, linenum, 'runtime/string', 4,
|
| 'For a static/global string constant, use a C style string instead: '
|
| '"%schar %s[]".' %
|
| (match.group(1), match.group(2)))
|
|
|
| - # Check that we're not using RTTI outside of testing code.
|
| - if Search(r'\bdynamic_cast<', line) and not _IsTestFilename(filename):
|
| - error(filename, linenum, 'runtime/rtti', 5,
|
| - 'Do not use dynamic_cast<>. If you need to cast within a class '
|
| - "hierarchy, use static_cast<> to upcast. Google doesn't support "
|
| - 'RTTI.')
|
| -
|
| if Search(r'\b([A-Za-z0-9_]*_)\(\1\)', line):
|
| error(filename, linenum, 'runtime/init', 4,
|
| 'You seem to be initializing a member variable with itself.')
|
| @@ -3323,10 +3904,6 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
|
| error(filename, linenum, 'runtime/printf', 4,
|
| 'Almost always, snprintf is better than %s' % match.group(1))
|
|
|
| - if Search(r'\bsscanf\b', line):
|
| - error(filename, linenum, 'runtime/printf', 1,
|
| - 'sscanf can be ok, but is slow and can overflow buffers.')
|
| -
|
| # Check if some verboten operator overloading is going on
|
| # TODO(unknown): catch out-of-line unary operator&:
|
| # class X {};
|
| @@ -3442,13 +4019,123 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
|
| 'http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces'
|
| ' for more information.')
|
|
|
| +def CheckForNonConstReference(filename, clean_lines, linenum,
|
| + nesting_state, error):
|
| + """Check for non-const references.
|
| +
|
| + Separate from CheckLanguage since it scans backwards from current
|
| + line, instead of scanning forward.
|
| +
|
| + Args:
|
| + filename: The name of the current file.
|
| + clean_lines: A CleansedLines instance containing the file.
|
| + linenum: The number of the line to check.
|
| + nesting_state: A _NestingState instance which maintains information about
|
| + the current stack of nested blocks being parsed.
|
| + error: The function to call with any errors found.
|
| + """
|
| + # Do nothing if there is no '&' on current line.
|
| + line = clean_lines.elided[linenum]
|
| + if '&' not in line:
|
| + return
|
| +
|
| + # Long type names may be broken across multiple lines, usually in one
|
| + # of these forms:
|
| + # LongType
|
| + # ::LongTypeContinued &identifier
|
| + # LongType::
|
| + # LongTypeContinued &identifier
|
| + # LongType<
|
| + # ...>::LongTypeContinued &identifier
|
| + #
|
| + # If we detected a type split across two lines, join the previous
|
| + # line to current line so that we can match const references
|
| + # accordingly.
|
| + #
|
| + # Note that this only scans back one line, since scanning back
|
| + # arbitrary number of lines would be expensive. If you have a type
|
| + # that spans more than 2 lines, please use a typedef.
|
| + if linenum > 1:
|
| + previous = None
|
| + if Match(r'\s*::(?:[\w<>]|::)+\s*&\s*\S', line):
|
| + # previous_line\n + ::current_line
|
| + previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+[\w<>])\s*$',
|
| + clean_lines.elided[linenum - 1])
|
| + elif Match(r'\s*[a-zA-Z_]([\w<>]|::)+\s*&\s*\S', line):
|
| + # previous_line::\n + current_line
|
| + previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+::)\s*$',
|
| + clean_lines.elided[linenum - 1])
|
| + if previous:
|
| + line = previous.group(1) + line.lstrip()
|
| + else:
|
| + # Check for templated parameter that is split across multiple lines
|
| + endpos = line.rfind('>')
|
| + if endpos > -1:
|
| + (_, startline, startpos) = ReverseCloseExpression(
|
| + clean_lines, linenum, endpos)
|
| + if startpos > -1 and startline < linenum:
|
| + # Found the matching < on an earlier line, collect all
|
| + # pieces up to current line.
|
| + line = ''
|
| + for i in xrange(startline, linenum + 1):
|
| + line += clean_lines.elided[i].strip()
|
| +
|
| + # Check for non-const references in function parameters. A single '&' may
|
| + # found in the following places:
|
| + # inside expression: binary & for bitwise AND
|
| + # inside expression: unary & for taking the address of something
|
| + # inside declarators: reference parameter
|
| + # We will exclude the first two cases by checking that we are not inside a
|
| + # function body, including one that was just introduced by a trailing '{'.
|
| + # TODO(unknwon): Doesn't account for preprocessor directives.
|
| + # TODO(unknown): Doesn't account for 'catch(Exception& e)' [rare].
|
| + check_params = False
|
| + if not nesting_state.stack:
|
| + check_params = True # top level
|
| + elif (isinstance(nesting_state.stack[-1], _ClassInfo) or
|
| + isinstance(nesting_state.stack[-1], _NamespaceInfo)):
|
| + check_params = True # within class or namespace
|
| + elif Match(r'.*{\s*$', line):
|
| + if (len(nesting_state.stack) == 1 or
|
| + isinstance(nesting_state.stack[-2], _ClassInfo) or
|
| + isinstance(nesting_state.stack[-2], _NamespaceInfo)):
|
| + check_params = True # just opened global/class/namespace block
|
| + # We allow non-const references in a few standard places, like functions
|
| + # called "swap()" or iostream operators like "<<" or ">>". Do not check
|
| + # those function parameters.
|
| + #
|
| + # We also accept & in static_assert, which looks like a function but
|
| + # it's actually a declaration expression.
|
| + whitelisted_functions = (r'(?:[sS]wap(?:<\w:+>)?|'
|
| + r'operator\s*[<>][<>]|'
|
| + r'static_assert|COMPILE_ASSERT'
|
| + r')\s*\(')
|
| + if Search(whitelisted_functions, line):
|
| + check_params = False
|
| + elif not Search(r'\S+\([^)]*$', line):
|
| + # Don't see a whitelisted function on this line. Actually we
|
| + # didn't see any function name on this line, so this is likely a
|
| + # multi-line parameter list. Try a bit harder to catch this case.
|
| + for i in xrange(2):
|
| + if (linenum > i and
|
| + Search(whitelisted_functions, clean_lines.elided[linenum - i - 1])):
|
| + check_params = False
|
| + break
|
| +
|
| + if check_params:
|
| + decls = ReplaceAll(r'{[^}]*}', ' ', line) # exclude function body
|
| + for parameter in re.findall(_RE_PATTERN_REF_PARAM, decls):
|
| + if not Match(_RE_PATTERN_CONST_REF_PARAM, parameter):
|
| + error(filename, linenum, 'runtime/references', 2,
|
| + 'Is this a non-const reference? '
|
| + 'If so, make const or use a pointer: ' +
|
| + ReplaceAll(' *<', '<', parameter))
|
| +
|
|
|
| def CheckCStyleCast(filename, linenum, line, raw_line, cast_type, pattern,
|
| error):
|
| """Checks for a C-style cast by looking for the pattern.
|
|
|
| - This also handles sizeof(type) warnings, due to similarity of content.
|
| -
|
| Args:
|
| filename: The name of the current file.
|
| linenum: The number of the line to check.
|
| @@ -3467,40 +4154,68 @@ def CheckCStyleCast(filename, linenum, line, raw_line, cast_type, pattern,
|
| if not match:
|
| return False
|
|
|
| - # e.g., sizeof(int)
|
| + # Exclude lines with sizeof, since sizeof looks like a cast.
|
| sizeof_match = Match(r'.*sizeof\s*$', line[0:match.start(1) - 1])
|
| if sizeof_match:
|
| - error(filename, linenum, 'runtime/sizeof', 1,
|
| - 'Using sizeof(type). Use sizeof(varname) instead if possible')
|
| - return True
|
| + return False
|
|
|
| # operator++(int) and operator--(int)
|
| if (line[0:match.start(1) - 1].endswith(' operator++') or
|
| line[0:match.start(1) - 1].endswith(' operator--')):
|
| return False
|
|
|
| + # A single unnamed argument for a function tends to look like old
|
| + # style cast. If we see those, don't issue warnings for deprecated
|
| + # casts, instead issue warnings for unnamed arguments where
|
| + # appropriate.
|
| + #
|
| + # These are things that we want warnings for, since the style guide
|
| + # explicitly require all parameters to be named:
|
| + # Function(int);
|
| + # Function(int) {
|
| + # ConstMember(int) const;
|
| + # ConstMember(int) const {
|
| + # ExceptionMember(int) throw (...);
|
| + # ExceptionMember(int) throw (...) {
|
| + # PureVirtual(int) = 0;
|
| + #
|
| + # These are functions of some sort, where the compiler would be fine
|
| + # if they had named parameters, but people often omit those
|
| + # identifiers to reduce clutter:
|
| + # (FunctionPointer)(int);
|
| + # (FunctionPointer)(int) = value;
|
| + # Function((function_pointer_arg)(int))
|
| + # <TemplateArgument(int)>;
|
| + # <(FunctionPointerTemplateArgument)(int)>;
|
| remainder = line[match.end(0):]
|
| + if Match(r'^\s*(?:;|const\b|throw\b|=|>|\{|\))', remainder):
|
| + # Looks like an unnamed parameter.
|
|
|
| - # The close paren is for function pointers as arguments to a function.
|
| - # eg, void foo(void (*bar)(int));
|
| - # The semicolon check is a more basic function check; also possibly a
|
| - # function pointer typedef.
|
| - # eg, void foo(int); or void foo(int) const;
|
| - # The equals check is for function pointer assignment.
|
| - # eg, void *(*foo)(int) = ...
|
| - # The > is for MockCallback<...> ...
|
| - #
|
| - # Right now, this will only catch cases where there's a single argument, and
|
| - # it's unnamed. It should probably be expanded to check for multiple
|
| - # arguments with some unnamed.
|
| - function_match = Match(r'\s*(\)|=|(const)?\s*(;|\{|throw\(\)|>))', remainder)
|
| - if function_match:
|
| - if (not function_match.group(3) or
|
| - function_match.group(3) == ';' or
|
| - ('MockCallback<' not in raw_line and
|
| - '/*' not in raw_line)):
|
| - error(filename, linenum, 'readability/function', 3,
|
| - 'All parameters should be named in a function')
|
| + # Don't warn on any kind of template arguments.
|
| + if Match(r'^\s*>', remainder):
|
| + return False
|
| +
|
| + # Don't warn on assignments to function pointers, but keep warnings for
|
| + # unnamed parameters to pure virtual functions. Note that this pattern
|
| + # will also pass on assignments of "0" to function pointers, but the
|
| + # preferred values for those would be "nullptr" or "NULL".
|
| + matched_zero = Match(r'^\s=\s*(\S+)\s*;', remainder)
|
| + if matched_zero and matched_zero.group(1) != '0':
|
| + return False
|
| +
|
| + # Don't warn on function pointer declarations. For this we need
|
| + # to check what came before the "(type)" string.
|
| + if Match(r'.*\)\s*$', line[0:match.start(0)]):
|
| + return False
|
| +
|
| + # Don't warn if the parameter is named with block comments, e.g.:
|
| + # Function(int /*unused_param*/);
|
| + if '/*' in raw_line:
|
| + return False
|
| +
|
| + # Passed all filters, issue warning here.
|
| + error(filename, linenum, 'readability/function', 3,
|
| + 'All parameters should be named in a function')
|
| return True
|
|
|
| # At this point, all that should be left is actual casts.
|
| @@ -3761,8 +4476,7 @@ def CheckMakePairUsesDeduction(filename, clean_lines, linenum, error):
|
| linenum: The number of the line to check.
|
| error: The function to call with any errors found.
|
| """
|
| - raw = clean_lines.raw_lines
|
| - line = raw[linenum]
|
| + line = clean_lines.elided[linenum]
|
| match = _RE_PATTERN_EXPLICIT_MAKEPAIR.search(line)
|
| if match:
|
| error(filename, linenum, 'build/explicit_make_pair',
|
| @@ -3801,9 +4515,11 @@ def ProcessLine(filename, file_extension, clean_lines, line,
|
| CheckForMultilineCommentsAndStrings(filename, clean_lines, line, error)
|
| CheckStyle(filename, clean_lines, line, file_extension, nesting_state, error)
|
| CheckLanguage(filename, clean_lines, line, file_extension, include_state,
|
| - error)
|
| + nesting_state, error)
|
| + CheckForNonConstReference(filename, clean_lines, line, nesting_state, error)
|
| CheckForNonStandardConstructs(filename, clean_lines, line,
|
| nesting_state, error)
|
| + CheckVlogArguments(filename, clean_lines, line, error)
|
| CheckPosixThreading(filename, clean_lines, line, error)
|
| CheckInvalidIncrement(filename, clean_lines, line, error)
|
| CheckMakePairUsesDeduction(filename, clean_lines, line, error)
|
| @@ -3845,13 +4561,13 @@ def ProcessFileData(filename, file_extension, lines, error,
|
| ProcessLine(filename, file_extension, clean_lines, line,
|
| include_state, function_state, nesting_state, error,
|
| extra_check_functions)
|
| - nesting_state.CheckClassFinished(filename, error)
|
| + nesting_state.CheckCompletedBlocks(filename, error)
|
|
|
| CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error)
|
|
|
| # We check here rather than inside ProcessLine so that we see raw
|
| # lines rather than "cleaned" lines.
|
| - CheckForUnicodeReplacementCharacters(filename, lines, error)
|
| + CheckForBadCharacters(filename, lines, error)
|
|
|
| CheckForNewlineAtEOF(filename, lines, error)
|
|
|
| @@ -3907,9 +4623,9 @@ def ProcessFile(filename, vlevel, extra_check_functions=[]):
|
|
|
| # When reading from stdin, the extension is unknown, so no cpplint tests
|
| # should rely on the extension.
|
| - if (filename != '-' and file_extension != 'cc' and file_extension != 'h'
|
| - and file_extension != 'cpp'):
|
| - sys.stderr.write('Ignoring %s; not a .cc or .h file\n' % filename)
|
| + if filename != '-' and file_extension not in _valid_extensions:
|
| + sys.stderr.write('Ignoring %s; not a valid file name '
|
| + '(%s)\n' % (filename, ', '.join(_valid_extensions)))
|
| else:
|
| ProcessFileData(filename, file_extension, lines, Error,
|
| extra_check_functions)
|
| @@ -3960,7 +4676,9 @@ def ParseArguments(args):
|
| (opts, filenames) = getopt.getopt(args, '', ['help', 'output=', 'verbose=',
|
| 'counting=',
|
| 'filter=',
|
| - 'root='])
|
| + 'root=',
|
| + 'linelength=',
|
| + 'extensions='])
|
| except getopt.GetoptError:
|
| PrintUsage('Invalid arguments.')
|
|
|
| @@ -3973,7 +4691,7 @@ def ParseArguments(args):
|
| if opt == '--help':
|
| PrintUsage(None)
|
| elif opt == '--output':
|
| - if not val in ('emacs', 'vs7', 'eclipse'):
|
| + if val not in ('emacs', 'vs7', 'eclipse'):
|
| PrintUsage('The only allowed output formats are emacs, vs7 and eclipse.')
|
| output_format = val
|
| elif opt == '--verbose':
|
| @@ -3989,6 +4707,18 @@ def ParseArguments(args):
|
| elif opt == '--root':
|
| global _root
|
| _root = val
|
| + elif opt == '--linelength':
|
| + global _line_length
|
| + try:
|
| + _line_length = int(val)
|
| + except ValueError:
|
| + PrintUsage('Line length must be digits.')
|
| + elif opt == '--extensions':
|
| + global _valid_extensions
|
| + try:
|
| + _valid_extensions = set(val.split(','))
|
| + except ValueError:
|
| + PrintUsage('Extensions must be comma seperated list.')
|
|
|
| if not filenames:
|
| PrintUsage('No files were specified.')
|
|
|