Add lint check against "Foo *bar" and "Foo &bar" declarations.

Depends on extension mechanism for cpplint.py: http://codereview.appspot.com/4950069/

Pulls r74 of cpplint.py from:
http://google-styleguide.googlecode.com/svn-history/r74/trunk/cpplint/cpplint.py

Taken from WebKit's fork of cpplint.py.

WebKit patch was: http://trac.webkit.org/changeset/46856

Credit Torch Mobile, Inc. who have contributed the WebKit patch in question.

BUG=none
TEST=Run gcl lint on a CL that has a Foo *bar style declaration.

Review URL: http://codereview.chromium.org/7834045

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@100151 0039d316-1c4b-4281-b951-d872f2087c98
parent 8baaea7b
...@@ -150,6 +150,7 @@ _ERROR_CATEGORIES = [ ...@@ -150,6 +150,7 @@ _ERROR_CATEGORIES = [
'build/class', 'build/class',
'build/deprecated', 'build/deprecated',
'build/endif_comment', 'build/endif_comment',
'build/explicit_make_pair',
'build/forward_decl', 'build/forward_decl',
'build/header_guard', 'build/header_guard',
'build/include', 'build/include',
...@@ -210,11 +211,11 @@ _ERROR_CATEGORIES = [ ...@@ -210,11 +211,11 @@ _ERROR_CATEGORIES = [
# flag. By default all errors are on, so only add here categories that should be # 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). # 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. # All entries here should start with a '-' or '+', as in the --filter= flag.
_DEFAULT_FILTERS = [ '-build/include_alpha' ] _DEFAULT_FILTERS = ['-build/include_alpha']
# 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 separate i18n file.
# Headers that we consider STL headers. # Headers that we consider STL headers.
_STL_HEADERS = frozenset([ _STL_HEADERS = frozenset([
...@@ -235,11 +236,11 @@ _CPP_HEADERS = frozenset([ ...@@ -235,11 +236,11 @@ _CPP_HEADERS = frozenset([
'cstdio', 'cstdlib', 'cstring', 'ctime', 'cwchar', 'cwctype', 'cstdio', 'cstdlib', 'cstring', 'ctime', 'cwchar', 'cwctype',
'defalloc.h', 'deque.h', 'editbuf.h', 'exception', 'fstream', 'defalloc.h', 'deque.h', 'editbuf.h', 'exception', 'fstream',
'fstream.h', 'hashtable.h', 'heap.h', 'indstream.h', 'iomanip', 'fstream.h', 'hashtable.h', 'heap.h', 'indstream.h', 'iomanip',
'iomanip.h', 'ios', 'iosfwd', 'iostream', 'iostream.h', 'istream.h', 'iomanip.h', 'ios', 'iosfwd', 'iostream', 'iostream.h', 'istream',
'iterator.h', 'limits', 'map.h', 'multimap.h', 'multiset.h', 'istream.h', 'iterator.h', 'limits', 'map.h', 'multimap.h', 'multiset.h',
'numeric', 'ostream.h', 'parsestream.h', 'pfstream.h', 'PlotFile.h', 'numeric', 'ostream', 'ostream.h', 'parsestream.h', 'pfstream.h',
'procbuf.h', 'pthread_alloc.h', 'rope', 'rope.h', 'ropeimpl.h', 'PlotFile.h', 'procbuf.h', 'pthread_alloc.h', 'rope', 'rope.h',
'SFile.h', 'slist', 'slist.h', 'stack.h', 'stdexcept', 'ropeimpl.h', 'SFile.h', 'slist', 'slist.h', 'stack.h', 'stdexcept',
'stdiostream.h', 'streambuf.h', 'stream.h', 'strfile.h', 'string', 'stdiostream.h', 'streambuf.h', 'stream.h', 'strfile.h', 'string',
'strstream', 'strstream.h', 'tempbuf.h', 'tree.h', 'typeinfo', 'valarray', 'strstream', 'strstream.h', 'tempbuf.h', 'tree.h', 'typeinfo', 'valarray',
]) ])
...@@ -310,9 +311,9 @@ def ParseNolintSuppressions(filename, raw_line, linenum, error): ...@@ -310,9 +311,9 @@ def ParseNolintSuppressions(filename, raw_line, linenum, error):
error: function, an error handler. error: function, an error handler.
""" """
# FIXME(adonovan): "NOLINT(" is misparsed as NOLINT(*). # FIXME(adonovan): "NOLINT(" is misparsed as NOLINT(*).
m = _RE_SUPPRESSION.search(raw_line) matched = _RE_SUPPRESSION.search(raw_line)
if m: if matched:
category = m.group(1) category = matched.group(1)
if category in (None, '(*)'): # => "suppress all" if category in (None, '(*)'): # => "suppress all"
_error_suppressions.setdefault(None, set()).add(linenum) _error_suppressions.setdefault(None, set()).add(linenum)
else: else:
...@@ -404,7 +405,7 @@ class _IncludeState(dict): ...@@ -404,7 +405,7 @@ class _IncludeState(dict):
self._last_header = '' self._last_header = ''
def CanonicalizeAlphabeticalOrder(self, header_path): def CanonicalizeAlphabeticalOrder(self, header_path):
"""Returns a path canonicalized for alphabetical comparisson. """Returns a path canonicalized for alphabetical comparison.
- replaces "-" with "_" so they both cmp the same. - replaces "-" with "_" so they both cmp the same.
- removes '-inl' since we don't require them to be after the main header. - removes '-inl' since we don't require them to be after the main header.
...@@ -662,7 +663,7 @@ class _FunctionState(object): ...@@ -662,7 +663,7 @@ class _FunctionState(object):
self.current_function, self.lines_in_function, trigger)) self.current_function, self.lines_in_function, trigger))
def End(self): def End(self):
"""Stop analizing function body.""" """Stop analyzing function body."""
self.in_a_function = False self.in_a_function = False
...@@ -760,8 +761,7 @@ class FileInfo: ...@@ -760,8 +761,7 @@ class FileInfo:
def _ShouldPrintError(category, confidence, linenum): def _ShouldPrintError(category, confidence, linenum):
"""Returns true iff confidence >= verbose, category passes """If confidence >= verbose, category passes filter and is not suppressed."""
filter and is not NOLINT-suppressed."""
# There are three ways we might decide not to print an error message: # There are three ways we might decide not to print an error message:
# a "NOLINT(category)" comment appears in the source, # a "NOLINT(category)" comment appears in the source,
...@@ -913,7 +913,7 @@ def CleanseComments(line): ...@@ -913,7 +913,7 @@ def CleanseComments(line):
""" """
commentpos = line.find('//') commentpos = line.find('//')
if commentpos != -1 and not IsCppString(line[:commentpos]): if commentpos != -1 and not IsCppString(line[:commentpos]):
line = line[:commentpos] line = line[:commentpos].rstrip()
# get rid of /* ... */ # get rid of /* ... */
return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub('', line) return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub('', line)
...@@ -966,7 +966,7 @@ class CleansedLines(object): ...@@ -966,7 +966,7 @@ class CleansedLines(object):
def CloseExpression(clean_lines, linenum, pos): def CloseExpression(clean_lines, linenum, pos):
"""If input points to ( or { or [, finds the position that closes it. """If input points to ( or { or [, finds the position that closes it.
If lines[linenum][pos] points to a '(' or '{' or '[', finds the the If lines[linenum][pos] points to a '(' or '{' or '[', finds the
linenum/pos that correspond to the closing of the expression. linenum/pos that correspond to the closing of the expression.
Args: Args:
...@@ -1248,7 +1248,7 @@ def CheckInvalidIncrement(filename, clean_lines, linenum, error): ...@@ -1248,7 +1248,7 @@ def CheckInvalidIncrement(filename, clean_lines, linenum, error):
class _ClassInfo(object): class _ClassInfo(object):
"""Stores information about a class.""" """Stores information about a class."""
def __init__(self, name, linenum): def __init__(self, name, clean_lines, linenum):
self.name = name self.name = name
self.linenum = linenum self.linenum = linenum
self.seen_open_brace = False self.seen_open_brace = False
...@@ -1257,6 +1257,20 @@ class _ClassInfo(object): ...@@ -1257,6 +1257,20 @@ class _ClassInfo(object):
self.has_virtual_destructor = False self.has_virtual_destructor = False
self.brace_depth = 0 self.brace_depth = 0
# Try to find the end of the class. This will be confused by things like:
# class A {
# } *x = { ...
#
# But it's still good enough for CheckSectionSpacing.
self.last_line = 0
depth = 0
for i in range(linenum, clean_lines.NumLines()):
line = clean_lines.lines[i]
depth += line.count('{') - line.count('}')
if not depth:
self.last_line = i
break
class _ClassState(object): class _ClassState(object):
"""Holds the current state of the parse relating to class declarations. """Holds the current state of the parse relating to class declarations.
...@@ -1378,11 +1392,16 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, ...@@ -1378,11 +1392,16 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum,
# style guidelines, but it seems to perform well enough in testing # style guidelines, but it seems to perform well enough in testing
# to be a worthwhile addition to the checks. # to be a worthwhile addition to the checks.
classinfo_stack = class_state.classinfo_stack classinfo_stack = class_state.classinfo_stack
# Look for a class declaration # Look for a class declaration. The regexp accounts for decorated classes
# such as in:
# class LOCKABLE API Object {
# };
class_decl_match = Match( class_decl_match = Match(
r'\s*(template\s*<[\w\s<>,:]*>\s*)?(class|struct)\s+(\w+(::\w+)*)', line) r'\s*(template\s*<[\w\s<>,:]*>\s*)?'
'(class|struct)\s+([A-Z_]+\s+)*(\w+(::\w+)*)', line)
if class_decl_match: if class_decl_match:
classinfo_stack.append(_ClassInfo(class_decl_match.group(3), linenum)) classinfo_stack.append(_ClassInfo(
class_decl_match.group(4), clean_lines, linenum))
# Everything else in this function uses the top of the stack if it's # Everything else in this function uses the top of the stack if it's
# not empty. # not empty.
...@@ -1412,12 +1431,12 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, ...@@ -1412,12 +1431,12 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum,
# Look for single-argument constructors that aren't marked explicit. # Look for single-argument constructors that aren't marked explicit.
# Technically a valid construct, but against style. # Technically a valid construct, but against style.
args = Match(r'(?<!explicit)\s+%s\s*\(([^,()]+)\)' args = Match(r'\s+(?:inline\s+)?%s\s*\(([^,()]+)\)'
% re.escape(base_classname), % re.escape(base_classname),
line) line)
if (args and if (args and
args.group(1) != 'void' and args.group(1) != 'void' and
not Match(r'(const\s+)?%s\s*&' % re.escape(base_classname), not Match(r'(const\s+)?%s\s*(?:<\w+>\s*)?&' % re.escape(base_classname),
args.group(1).strip())): args.group(1).strip())):
error(filename, linenum, 'runtime/explicit', 5, error(filename, linenum, 'runtime/explicit', 5,
'Single-argument constructors should be marked explicit.') 'Single-argument constructors should be marked explicit.')
...@@ -1508,6 +1527,12 @@ def CheckSpacingForFunctionCall(filename, line, linenum, error): ...@@ -1508,6 +1527,12 @@ def CheckSpacingForFunctionCall(filename, line, linenum, error):
# If the ) is followed only by a newline or a { + newline, assume it's # If the ) is followed only by a newline or a { + newline, assume it's
# part of a control statement (if/while/etc), and don't complain # part of a control statement (if/while/etc), and don't complain
if Search(r'[^)]\s+\)\s*[^{\s]', fncall): if Search(r'[^)]\s+\)\s*[^{\s]', fncall):
# If the closing parenthesis is preceded by only whitespaces,
# try to give a more descriptive error message.
if Search(r'^\s+\)', fncall):
error(filename, linenum, 'whitespace/parens', 2,
'Closing ) should be moved to the previous line')
else:
error(filename, linenum, 'whitespace/parens', 2, error(filename, linenum, 'whitespace/parens', 2,
'Extra space before )') 'Extra space before )')
...@@ -1540,7 +1565,7 @@ def CheckForFunctionLengths(filename, clean_lines, linenum, ...@@ -1540,7 +1565,7 @@ def CheckForFunctionLengths(filename, clean_lines, linenum,
Trivial bodies are unchecked, so constructors with huge initializer lists Trivial bodies are unchecked, so constructors with huge initializer lists
may be missed. may be missed.
Blank/comment lines are not counted so as to avoid encouraging the removal Blank/comment lines are not counted so as to avoid encouraging the removal
of vertical space and commments just to get through a lint check. of vertical space and comments just to get through a lint check.
NOLINT *on the last line of a function* disables this check. NOLINT *on the last line of a function* disables this check.
Args: Args:
...@@ -1636,8 +1661,8 @@ def CheckSpacing(filename, clean_lines, linenum, error): ...@@ -1636,8 +1661,8 @@ def CheckSpacing(filename, clean_lines, linenum, error):
Things we check for: spaces around operators, spaces after Things we check for: spaces around operators, spaces after
if/for/while/switch, no spaces around parens in function calls, two if/for/while/switch, no spaces around parens in function calls, two
spaces between code and comment, don't start a block with a blank spaces between code and comment, don't start a block with a blank
line, don't end a function with a blank line, don't have too many line, don't end a function with a blank line, don't add a blank line
blank lines in a row. after public/protected/private, don't have too many blank lines in a row.
Args: Args:
filename: The name of the current file. filename: The name of the current file.
...@@ -1664,7 +1689,7 @@ def CheckSpacing(filename, clean_lines, linenum, error): ...@@ -1664,7 +1689,7 @@ def CheckSpacing(filename, clean_lines, linenum, error):
and prev_line[:prevbrace].find('namespace') == -1): and prev_line[:prevbrace].find('namespace') == -1):
# OK, we have a blank line at the start of a code block. Before we # OK, we have a blank line at the start of a code block. Before we
# complain, we check if it is an exception to the rule: The previous # complain, we check if it is an exception to the rule: The previous
# non-empty line has the paramters of a function header that are indented # non-empty line has the parameters of a function header that are indented
# 4 spaces (because they did not fit in a 80 column line when placed on # 4 spaces (because they did not fit in a 80 column line when placed on
# the same line as the function name). We also check for the case where # the same line as the function name). We also check for the case where
# the previous line is indented 6 spaces, which may happen when the # the previous line is indented 6 spaces, which may happen when the
...@@ -1715,6 +1740,11 @@ def CheckSpacing(filename, clean_lines, linenum, error): ...@@ -1715,6 +1740,11 @@ def CheckSpacing(filename, clean_lines, linenum, error):
error(filename, linenum, 'whitespace/blank_line', 3, error(filename, linenum, 'whitespace/blank_line', 3,
'Blank line at the end of a code block. Is this needed?') 'Blank line at the end of a code block. Is this needed?')
matched = Match(r'\s*(public|protected|private):', prev_line)
if matched:
error(filename, linenum, 'whitespace/blank_line', 3,
'Do not leave a blank line after "%s:"' % matched.group(1))
# Next, we complain if there's a comment too near the text # Next, we complain if there's a comment too near the text
commentpos = line.find('//') commentpos = line.find('//')
if commentpos != -1: if commentpos != -1:
...@@ -1824,13 +1854,22 @@ def CheckSpacing(filename, clean_lines, linenum, error): ...@@ -1824,13 +1854,22 @@ def CheckSpacing(filename, clean_lines, linenum, error):
error(filename, linenum, 'whitespace/comma', 3, error(filename, linenum, 'whitespace/comma', 3,
'Missing space after ,') 'Missing space after ,')
# You should always have a space after a semicolon
# except for few corner cases
# TODO(unknown): clarify if 'if (1) { return 1;}' is requires one more
# space after ;
if Search(r';[^\s};\\)/]', line):
error(filename, linenum, 'whitespace/semicolon', 3,
'Missing space after ;')
# Next we will look for issues with function calls. # Next we will look for issues with function calls.
CheckSpacingForFunctionCall(filename, line, linenum, error) CheckSpacingForFunctionCall(filename, line, linenum, error)
# Except after an opening paren, you should have spaces before your braces. # Except after an opening paren, or after another opening brace (in case of
# And since you should never have braces at the beginning of a line, this is # an initializer list, for instance), you should have spaces before your
# an easy test. # braces. And since you should never have braces at the beginning of a line,
if Search(r'[^ (]{', line): # this is an easy test.
if Search(r'[^ ({]{', line):
error(filename, linenum, 'whitespace/braces', 5, error(filename, linenum, 'whitespace/braces', 5,
'Missing space before {') 'Missing space before {')
...@@ -1862,6 +1901,58 @@ def CheckSpacing(filename, clean_lines, linenum, error): ...@@ -1862,6 +1901,58 @@ def CheckSpacing(filename, clean_lines, linenum, error):
'statement, use { } instead.') 'statement, use { } instead.')
def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error):
"""Checks for additional blank line issues related to sections.
Currently the only thing checked here is blank line before protected/private.
Args:
filename: The name of the current file.
clean_lines: A CleansedLines instance containing the file.
class_info: A _ClassInfo objects.
linenum: The number of the line to check.
error: The function to call with any errors found.
"""
# Skip checks if the class is small, where small means 25 lines or less.
# 25 lines seems like a good cutoff since that's the usual height of
# terminals, and any class that can't fit in one screen can't really
# be considered "small".
#
# Also skip checks if we are on the first line. This accounts for
# classes that look like
# class Foo { public: ... };
#
# If we didn't find the end of the class, last_line would be zero,
# and the check will be skipped by the first condition.
if (class_info.last_line - class_info.linenum <= 24 or
linenum <= class_info.linenum):
return
matched = Match(r'\s*(public|protected|private):', clean_lines.lines[linenum])
if matched:
# Issue warning if the line before public/protected/private was
# not a blank line, but don't do this if the previous line contains
# "class" or "struct". This can happen two ways:
# - We are at the beginning of the class.
# - We are forward-declaring an inner class that is semantically
# private, but needed to be public for implementation reasons.
prev_line = clean_lines.lines[linenum - 1]
if (not IsBlankLine(prev_line) and
not Search(r'\b(class|struct)\b', prev_line)):
# Try a bit harder to find the beginning of the class. This is to
# account for multi-line base-specifier lists, e.g.:
# class Derived
# : public Base {
end_class_head = class_info.linenum
for i in range(class_info.linenum, linenum):
if Search(r'\{\s*$', clean_lines.lines[i]):
end_class_head = i
break
if end_class_head < linenum - 1:
error(filename, linenum, 'whitespace/blank_line', 3,
'"%s:" should be preceded by a blank line' % matched.group(1))
def GetPreviousNonBlankLine(clean_lines, linenum): def GetPreviousNonBlankLine(clean_lines, linenum):
"""Return the most recent non-blank line and its line number. """Return the most recent non-blank line and its line number.
...@@ -2039,17 +2130,18 @@ def GetLineWidth(line): ...@@ -2039,17 +2130,18 @@ def GetLineWidth(line):
""" """
if isinstance(line, unicode): if isinstance(line, unicode):
width = 0 width = 0
for c in unicodedata.normalize('NFC', line): for uc in unicodedata.normalize('NFC', line):
if unicodedata.east_asian_width(c) in ('W', 'F'): if unicodedata.east_asian_width(uc) in ('W', 'F'):
width += 2 width += 2
elif not unicodedata.combining(c): elif not unicodedata.combining(uc):
width += 1 width += 1
return width return width
else: else:
return len(line) return len(line)
def CheckStyle(filename, clean_lines, linenum, file_extension, error): def CheckStyle(filename, clean_lines, linenum, file_extension, class_state,
error):
"""Checks rules from the 'C++ style rules' section of cppguide.html. """Checks rules from the 'C++ style rules' section of cppguide.html.
Most of these rules are hard to test (naming, comment style), but we Most of these rules are hard to test (naming, comment style), but we
...@@ -2119,8 +2211,12 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, error): ...@@ -2119,8 +2211,12 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, error):
# #
# URLs can be long too. It's possible to split these, but it makes them # URLs can be long too. It's possible to split these, but it makes them
# harder to cut&paste. # harder to cut&paste.
#
# The "$Id:...$" comment may also get very long without it being the
# developers fault.
if (not line.startswith('#include') and not is_header_guard and if (not line.startswith('#include') and not is_header_guard and
not Match(r'^\s*//.*http(s?)://\S*$', line)): not Match(r'^\s*//.*http(s?)://\S*$', line) and
not Match(r'^// \$Id:.*#[0-9]+ \$$', line)):
line_width = GetLineWidth(line) line_width = GetLineWidth(line)
if line_width > 100: if line_width > 100:
error(filename, linenum, 'whitespace/line_length', 4, error(filename, linenum, 'whitespace/line_length', 4,
...@@ -2145,6 +2241,9 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, error): ...@@ -2145,6 +2241,9 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, error):
CheckBraces(filename, clean_lines, linenum, error) CheckBraces(filename, clean_lines, linenum, error)
CheckSpacing(filename, clean_lines, linenum, error) CheckSpacing(filename, clean_lines, linenum, error)
CheckCheck(filename, clean_lines, linenum, error) CheckCheck(filename, clean_lines, linenum, error)
if class_state and class_state.classinfo_stack:
CheckSectionSpacing(filename, clean_lines,
class_state.classinfo_stack[-1], linenum, error)
_RE_PATTERN_INCLUDE_NEW_STYLE = re.compile(r'#include +"[^/]+\.h"') _RE_PATTERN_INCLUDE_NEW_STYLE = re.compile(r'#include +"[^/]+\.h"')
...@@ -2330,6 +2429,63 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): ...@@ -2330,6 +2429,63 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error):
error(filename, linenum, 'readability/streams', 3, error(filename, linenum, 'readability/streams', 3,
'Streams are highly discouraged.') 'Streams are highly discouraged.')
def _GetTextInside(text, start_pattern):
"""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
(, [, or {, and the matching close-punctuation symbol. This properly nested
occurrences of the punctuations, so for the text like
printf(a(), b(c()));
a call to _GetTextInside(text, r'printf\(') will return 'a(), b(c())'.
start_pattern must match string having an open punctuation symbol at the end.
Args:
text: The lines to extract text. Its comments and strings must be elided.
It can be single line and can span multiple lines.
start_pattern: The regexp string indicating where to start extracting
the text.
Returns:
The extracted text.
None if either the opening string or ending punctuation could not be found.
"""
# TODO(sugawarayu): Audit cpplint.py to see what places could be profitably
# rewritten to use _GetTextInside (and use inferior regexp matching today).
# Give opening punctuations to get the matching close-punctuations.
matching_punctuation = {'(': ')', '{': '}', '[': ']'}
closing_punctuation = set(matching_punctuation.itervalues())
# Find the position to start extracting text.
match = re.search(start_pattern, text, re.M)
if not match: # start_pattern not found in text.
return None
start_position = match.end(0)
assert start_position > 0, (
'start_pattern must ends with an opening punctuation.')
assert text[start_position - 1] in matching_punctuation, (
'start_pattern must ends with an opening punctuation.')
# Stack of closing punctuations we expect to have in text after position.
punctuation_stack = [matching_punctuation[text[start_position - 1]]]
position = start_position
while punctuation_stack and position < len(text):
if text[position] == punctuation_stack[-1]:
punctuation_stack.pop()
elif text[position] in closing_punctuation:
# A closing punctuation without matching opening punctuations.
return None
elif text[position] in matching_punctuation:
punctuation_stack.append(matching_punctuation[text[position]])
position += 1
if punctuation_stack:
# Opening punctuations left without matching close-punctuations.
return None
# punctuations match.
return text[start_position:position - 1]
def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state, def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
error): error):
"""Checks rules from the 'C++ language rules' section of cppguide.html. """Checks rules from the 'C++ language rules' section of cppguide.html.
...@@ -2375,7 +2531,7 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state, ...@@ -2375,7 +2531,7 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
# These are complicated re's. They try to capture the following: # These are complicated re's. They try to capture the following:
# paren (for fn-prototype start), typename, &, varname. For the const # paren (for fn-prototype start), typename, &, varname. For the const
# version, we're willing for const to be before typename or after # version, we're willing for const to be before typename or after
# Don't check the implemention on same line. # Don't check the implementation on same line.
fnline = line.split('{', 1)[0] fnline = line.split('{', 1)[0]
if (len(re.findall(r'\([^()]*\b(?:[\w:]|<[^()]*>)+(\s?&|&\s?)\w+', fnline)) > if (len(re.findall(r'\([^()]*\b(?:[\w:]|<[^()]*>)+(\s?&|&\s?)\w+', fnline)) >
len(re.findall(r'\([^()]*\bconst\s+(?:typename\s+)?(?:struct\s+)?' len(re.findall(r'\([^()]*\bconst\s+(?:typename\s+)?(?:struct\s+)?'
...@@ -2402,9 +2558,12 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state, ...@@ -2402,9 +2558,12 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
if match: if match:
# gMock methods are defined using some variant of MOCK_METHODx(name, type) # gMock methods are defined using some variant of MOCK_METHODx(name, type)
# where type may be float(), int(string), etc. Without context they are # where type may be float(), int(string), etc. Without context they are
# virtually indistinguishable from int(x) casts. # 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 if (match.group(1) is None and # If new operator, then this isn't a cast
not Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line)): not (Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line) or
Match(r'^\s*MockCallback<.*>', line))):
error(filename, linenum, 'readability/casting', 4, error(filename, linenum, 'readability/casting', 4,
'Using deprecated casting style. ' 'Using deprecated casting style. '
'Use static_cast<%s>(...) instead' % 'Use static_cast<%s>(...) instead' %
...@@ -2412,9 +2571,17 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state, ...@@ -2412,9 +2571,17 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
CheckCStyleCast(filename, linenum, line, clean_lines.raw_lines[linenum], CheckCStyleCast(filename, linenum, line, clean_lines.raw_lines[linenum],
'static_cast', 'static_cast',
r'\((int|float|double|bool|char|u?int(16|32|64))\)', r'\((int|float|double|bool|char|u?int(16|32|64))\)', error)
error)
# This doesn't catch all cases. Consider (const char * const)"hello". # This doesn't catch all cases. Consider (const char * const)"hello".
#
# (char *) "foo" should always be a const_cast (reinterpret_cast won't
# compile).
if CheckCStyleCast(filename, linenum, line, clean_lines.raw_lines[linenum],
'const_cast', r'\((char\s?\*+\s?)\)\s*"', error):
pass
else:
# Check pointer casts for other than string constants
CheckCStyleCast(filename, linenum, line, clean_lines.raw_lines[linenum], CheckCStyleCast(filename, linenum, line, clean_lines.raw_lines[linenum],
'reinterpret_cast', r'\((\w+\s?\*+\s?)\)', error) 'reinterpret_cast', r'\((\w+\s?\*+\s?)\)', error)
...@@ -2515,11 +2682,19 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state, ...@@ -2515,11 +2682,19 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
# Check for potential format string bugs like printf(foo). # Check for potential format string bugs like printf(foo).
# We constrain the pattern not to pick things like DocidForPrintf(foo). # We constrain the pattern not to pick things like DocidForPrintf(foo).
# Not perfect but it can catch printf(foo.c_str()) and printf(foo->c_str()) # Not perfect but it can catch printf(foo.c_str()) and printf(foo->c_str())
match = re.search(r'\b((?:string)?printf)\s*\(([\w.\->()]+)\)', line, re.I) # TODO(sugawarayu): Catch the following case. Need to change the calling
# convention of the whole function to process multiple line to handle it.
# printf(
# boy_this_is_a_really_long_variable_that_cannot_fit_on_the_prev_line);
printf_args = _GetTextInside(line, r'(?i)\b(string)?printf\s*\(')
if printf_args:
match = Match(r'([\w.\->()]+)$', printf_args)
if match: if match:
function_name = re.search(r'\b((?:string)?printf)\s*\(',
line, re.I).group(1)
error(filename, linenum, 'runtime/printf', 4, error(filename, linenum, 'runtime/printf', 4,
'Potential format string bug. Do %s("%%s", %s) instead.' 'Potential format string bug. Do %s("%%s", %s) instead.'
% (match.group(1), match.group(2))) % (function_name, match.group(1)))
# Check for potential memset bugs like memset(buf, sizeof(buf), 0). # Check for potential memset bugs like memset(buf, sizeof(buf), 0).
match = Search(r'memset\s*\(([^,]*),\s*([^,]*),\s*0\s*\)', line) match = Search(r'memset\s*\(([^,]*),\s*([^,]*),\s*0\s*\)', line)
...@@ -2561,7 +2736,7 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state, ...@@ -2561,7 +2736,7 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
if Match(r'(.+::)?[A-Z][A-Z0-9_]*', tok): continue if Match(r'(.+::)?[A-Z][A-Z0-9_]*', tok): continue
# A catch all for tricky sizeof cases, including 'sizeof expression', # A catch all for tricky sizeof cases, including 'sizeof expression',
# 'sizeof(*type)', 'sizeof(const type)', 'sizeof(struct StructName)' # 'sizeof(*type)', 'sizeof(const type)', 'sizeof(struct StructName)'
# requires skipping the next token becasue we split on ' ' and '*'. # requires skipping the next token because we split on ' ' and '*'.
if tok.startswith('sizeof'): if tok.startswith('sizeof'):
skip_next = True skip_next = True
continue continue
...@@ -2582,7 +2757,13 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state, ...@@ -2582,7 +2757,13 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, include_state,
line) line)
if match and linenum + 1 < clean_lines.NumLines(): if match and linenum + 1 < clean_lines.NumLines():
next_line = clean_lines.elided[linenum + 1] next_line = clean_lines.elided[linenum + 1]
if not Search(r'^\s*};', next_line): # We allow some, but not all, declarations of variables to be present
# in the statement that defines the class. The [\w\*,\s]* fragment of
# the regular expression below allows users to declare instances of
# the class or pointers to instances, but not less common types such
# as function pointers or arrays. It's a tradeoff between allowing
# reasonable code and avoiding trying to parse more C++ using regexps.
if not Search(r'^\s*}[\w\*,\s]*;', next_line):
error(filename, linenum, 'readability/constructors', 3, error(filename, linenum, 'readability/constructors', 3,
match.group(1) + ' should be the last thing in the class') match.group(1) + ' should be the last thing in the class')
...@@ -2610,20 +2791,24 @@ def CheckCStyleCast(filename, linenum, line, raw_line, cast_type, pattern, ...@@ -2610,20 +2791,24 @@ def CheckCStyleCast(filename, linenum, line, raw_line, cast_type, pattern,
line: The line of code to check. line: The line of code to check.
raw_line: The raw line of code to check, with comments. raw_line: The raw line of code to check, with comments.
cast_type: The string for the C++ cast to recommend. This is either cast_type: The string for the C++ cast to recommend. This is either
reinterpret_cast or static_cast, depending. reinterpret_cast, static_cast, or const_cast, depending.
pattern: The regular expression used to find C-style casts. pattern: The regular expression used to find C-style casts.
error: The function to call with any errors found. error: The function to call with any errors found.
Returns:
True if an error was emitted.
False otherwise.
""" """
match = Search(pattern, line) match = Search(pattern, line)
if not match: if not match:
return return False
# e.g., sizeof(int) # e.g., sizeof(int)
sizeof_match = Match(r'.*sizeof\s*$', line[0:match.start(1) - 1]) sizeof_match = Match(r'.*sizeof\s*$', line[0:match.start(1) - 1])
if sizeof_match: if sizeof_match:
error(filename, linenum, 'runtime/sizeof', 1, error(filename, linenum, 'runtime/sizeof', 1,
'Using sizeof(type). Use sizeof(varname) instead if possible') 'Using sizeof(type). Use sizeof(varname) instead if possible')
return return True
remainder = line[match.end(0):] remainder = line[match.end(0):]
...@@ -2634,24 +2819,28 @@ def CheckCStyleCast(filename, linenum, line, raw_line, cast_type, pattern, ...@@ -2634,24 +2819,28 @@ def CheckCStyleCast(filename, linenum, line, raw_line, cast_type, pattern,
# eg, void foo(int); or void foo(int) const; # eg, void foo(int); or void foo(int) const;
# The equals check is for function pointer assignment. # The equals check is for function pointer assignment.
# eg, void *(*foo)(int) = ... # eg, void *(*foo)(int) = ...
# The > is for MockCallback<...> ...
# #
# Right now, this will only catch cases where there's a single argument, and # 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 # it's unnamed. It should probably be expanded to check for multiple
# arguments with some unnamed. # arguments with some unnamed.
function_match = Match(r'\s*(\)|=|(const)?\s*(;|\{|throw\(\)))', remainder) function_match = Match(r'\s*(\)|=|(const)?\s*(;|\{|throw\(\)|>))', remainder)
if function_match: if function_match:
if (not function_match.group(3) or if (not function_match.group(3) or
function_match.group(3) == ';' or function_match.group(3) == ';' or
raw_line.find('/*') < 0): ('MockCallback<' not in raw_line and
'/*' not in raw_line)):
error(filename, linenum, 'readability/function', 3, error(filename, linenum, 'readability/function', 3,
'All parameters should be named in a function') 'All parameters should be named in a function')
return return True
# At this point, all that should be left is actual casts. # At this point, all that should be left is actual casts.
error(filename, linenum, 'readability/casting', 4, error(filename, linenum, 'readability/casting', 4,
'Using C-style cast. Use %s<%s>(...) instead' % 'Using C-style cast. Use %s<%s>(...) instead' %
(cast_type, match.group(1))) (cast_type, match.group(1)))
return True
_HEADERS_CONTAINING_TEMPLATES = ( _HEADERS_CONTAINING_TEMPLATES = (
('<deque>', ('deque',)), ('<deque>', ('deque',)),
...@@ -2690,11 +2879,6 @@ _HEADERS_CONTAINING_TEMPLATES = ( ...@@ -2690,11 +2879,6 @@ _HEADERS_CONTAINING_TEMPLATES = (
('<slist>', ('slist',)), ('<slist>', ('slist',)),
) )
_HEADERS_ACCEPTED_BUT_NOT_PROMOTED = {
# We can trust with reasonable confidence that map gives us pair<>, too.
'pair<>': ('map', 'multimap', 'hash_map', 'hash_multimap')
}
_RE_PATTERN_STRING = re.compile(r'\bstring\b') _RE_PATTERN_STRING = re.compile(r'\bstring\b')
_re_pattern_algorithm_header = [] _re_pattern_algorithm_header = []
...@@ -2827,11 +3011,11 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, ...@@ -2827,11 +3011,11 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error,
continue continue
# String is special -- it is a non-templatized type in STL. # String is special -- it is a non-templatized type in STL.
m = _RE_PATTERN_STRING.search(line) matched = _RE_PATTERN_STRING.search(line)
if m: if matched:
# Don't warn about strings in non-STL namespaces: # Don't warn about strings in non-STL namespaces:
# (We check only the first match per line; good enough.) # (We check only the first match per line; good enough.)
prefix = line[:m.start()] prefix = line[:matched.start()]
if prefix.endswith('std::') or not prefix.endswith('::'): if prefix.endswith('std::') or not prefix.endswith('::'):
required['<string>'] = (linenum, 'string') required['<string>'] = (linenum, 'string')
...@@ -2869,7 +3053,8 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, ...@@ -2869,7 +3053,8 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error,
# include_state is modified during iteration, so we iterate over a copy of # include_state is modified during iteration, so we iterate over a copy of
# the keys. # the keys.
for header in include_state.keys(): #NOLINT header_keys = include_state.keys()
for header in header_keys:
(same_module, common_path) = FilesBelongToSameModule(abs_filename, header) (same_module, common_path) = FilesBelongToSameModule(abs_filename, header)
fullpath = common_path + header fullpath = common_path + header
if same_module and UpdateIncludeState(fullpath, include_state, io): if same_module and UpdateIncludeState(fullpath, include_state, io):
...@@ -2886,19 +3071,40 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, ...@@ -2886,19 +3071,40 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error,
# 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]
if template in _HEADERS_ACCEPTED_BUT_NOT_PROMOTED:
headers = _HEADERS_ACCEPTED_BUT_NOT_PROMOTED[template]
if [True for header in headers if header in include_state]:
continue
if required_header_unstripped.strip('<>"') not in include_state: if required_header_unstripped.strip('<>"') not in include_state:
error(filename, required[required_header_unstripped][0], error(filename, required[required_header_unstripped][0],
'build/include_what_you_use', 4, 'build/include_what_you_use', 4,
'Add #include ' + required_header_unstripped + ' for ' + template) 'Add #include ' + required_header_unstripped + ' for ' + template)
_RE_PATTERN_EXPLICIT_MAKEPAIR = re.compile(r'\bmake_pair\s*<')
def CheckMakePairUsesDeduction(filename, clean_lines, linenum, error):
"""Check that make_pair's template arguments are deduced.
G++ 4.6 in C++0x mode fails badly if make_pair's template arguments are
specified explicitly, and such use isn't intended in any case.
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.
"""
raw = clean_lines.raw_lines
line = raw[linenum]
match = _RE_PATTERN_EXPLICIT_MAKEPAIR.search(line)
if match:
error(filename, linenum, 'build/explicit_make_pair',
4, # 4 = high confidence
'Omit template arguments from make_pair OR use pair directly OR'
' if appropriate, construct a pair directly')
def ProcessLine(filename, file_extension, def ProcessLine(filename, file_extension,
clean_lines, line, include_state, function_state, clean_lines, line, include_state, function_state,
class_state, error): class_state, error, extra_check_functions=[]):
"""Processes a single line in the file. """Processes a single line in the file.
Args: Args:
...@@ -2913,30 +3119,39 @@ def ProcessLine(filename, file_extension, ...@@ -2913,30 +3119,39 @@ def ProcessLine(filename, file_extension,
the current stack of nested class declarations being parsed. the current stack of nested class declarations being parsed.
error: A callable to which errors are reported, which takes 4 arguments: error: A callable to which errors are reported, which takes 4 arguments:
filename, line number, error level, and message filename, line number, error level, and message
extra_check_functions: An array of additional check functions that will be
run on each source line. Each function takes 4
arguments: filename, clean_lines, line, error
""" """
raw_lines = clean_lines.raw_lines raw_lines = clean_lines.raw_lines
ParseNolintSuppressions(filename, raw_lines[line], line, error) ParseNolintSuppressions(filename, raw_lines[line], line, error)
CheckForFunctionLengths(filename, clean_lines, line, function_state, error) CheckForFunctionLengths(filename, clean_lines, line, function_state, error)
CheckForMultilineCommentsAndStrings(filename, clean_lines, line, error) CheckForMultilineCommentsAndStrings(filename, clean_lines, line, error)
CheckStyle(filename, clean_lines, line, file_extension, error) CheckStyle(filename, clean_lines, line, file_extension, class_state, error)
CheckLanguage(filename, clean_lines, line, file_extension, include_state, CheckLanguage(filename, clean_lines, line, file_extension, include_state,
error) error)
CheckForNonStandardConstructs(filename, clean_lines, line, CheckForNonStandardConstructs(filename, clean_lines, line,
class_state, error) class_state, error)
CheckPosixThreading(filename, clean_lines, line, error) CheckPosixThreading(filename, clean_lines, line, error)
CheckInvalidIncrement(filename, clean_lines, line, error) CheckInvalidIncrement(filename, clean_lines, line, error)
CheckMakePairUsesDeduction(filename, clean_lines, line, error)
for check_fn in extra_check_functions:
check_fn(filename, clean_lines, line, error)
def ProcessFileData(filename, file_extension, lines, error,
def ProcessFileData(filename, file_extension, lines, error): extra_check_functions=[]):
"""Performs lint checks and reports any errors to the given error function. """Performs lint checks and reports any errors to the given error function.
Args: Args:
filename: Filename of the file that is being processed. filename: Filename of the file that is being processed.
file_extension: The extension (dot not included) of the file. file_extension: The extension (dot not included) of the file.
lines: An array of strings, each representing a line of the file, with the lines: An array of strings, each representing a line of the file, with the
last element being empty if the file is termined with a newline. last element being empty if the file is terminated with a newline.
error: A callable to which errors are reported, which takes 4 arguments: error: A callable to which errors are reported, which takes 4 arguments:
filename, line number, error level, and message
extra_check_functions: An array of additional check functions that will be
run on each source line. Each function takes 4
arguments: filename, clean_lines, line, error
""" """
lines = (['// marker so line numbers and indices both start at 1'] + lines + lines = (['// marker so line numbers and indices both start at 1'] + lines +
['// marker so line numbers end in a known way']) ['// marker so line numbers end in a known way'])
...@@ -2956,7 +3171,8 @@ def ProcessFileData(filename, file_extension, lines, error): ...@@ -2956,7 +3171,8 @@ def ProcessFileData(filename, file_extension, lines, error):
clean_lines = CleansedLines(lines) clean_lines = CleansedLines(lines)
for line in xrange(clean_lines.NumLines()): for line in xrange(clean_lines.NumLines()):
ProcessLine(filename, file_extension, clean_lines, line, ProcessLine(filename, file_extension, clean_lines, line,
include_state, function_state, class_state, error) include_state, function_state, class_state, error,
extra_check_functions)
class_state.CheckFinished(filename, error) class_state.CheckFinished(filename, error)
CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error)
...@@ -2967,7 +3183,7 @@ def ProcessFileData(filename, file_extension, lines, error): ...@@ -2967,7 +3183,7 @@ def ProcessFileData(filename, file_extension, lines, error):
CheckForNewlineAtEOF(filename, lines, error) CheckForNewlineAtEOF(filename, lines, error)
def ProcessFile(filename, vlevel): def ProcessFile(filename, vlevel, extra_check_functions=[]):
"""Does google-lint on a single file. """Does google-lint on a single file.
Args: Args:
...@@ -2975,6 +3191,10 @@ def ProcessFile(filename, vlevel): ...@@ -2975,6 +3191,10 @@ def ProcessFile(filename, vlevel):
vlevel: The level of errors to report. Every error of confidence vlevel: The level of errors to report. Every error of confidence
>= verbose_level will be reported. 0 is a good default. >= verbose_level will be reported. 0 is a good default.
extra_check_functions: An array of additional check functions that will be
run on each source line. Each function takes 4
arguments: filename, clean_lines, line, error
""" """
_SetVerboseLevel(vlevel) _SetVerboseLevel(vlevel)
...@@ -3019,9 +3239,10 @@ def ProcessFile(filename, vlevel): ...@@ -3019,9 +3239,10 @@ def ProcessFile(filename, vlevel):
and file_extension != 'cpp'): and file_extension != 'cpp'):
sys.stderr.write('Ignoring %s; not a .cc or .h file\n' % filename) sys.stderr.write('Ignoring %s; not a .cc or .h file\n' % filename)
else: else:
ProcessFileData(filename, file_extension, lines, Error) ProcessFileData(filename, file_extension, lines, Error,
extra_check_functions)
if carriage_return_found and os.linesep != '\r\n': if carriage_return_found and os.linesep != '\r\n':
# Use 0 for linenum since outputing only one error for potentially # Use 0 for linenum since outputting only one error for potentially
# several lines. # several lines.
Error(filename, 0, 'whitespace/newline', 1, Error(filename, 0, 'whitespace/newline', 1,
'One or more unexpected \\r (^M) found;' 'One or more unexpected \\r (^M) found;'
......
# Copyright (c) 2011 Google Inc. All rights reserved.
# Copyright (c) 2009 Torch Mobile Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import re
# Matches Foo *foo declarations.
_RE_PATTERN_POINTER_DECLARATION_WHITESPACE = re.compile(
r'\s*\w+(?<!\breturn|\bdelete)\s+(?P<pointer_operator>\*|\&)\w+')
def CheckPointerDeclarationWhitespace(filename, clean_lines, linenum, error):
"""Checks for Foo *foo declarations.
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]
matched = _RE_PATTERN_POINTER_DECLARATION_WHITESPACE.match(line)
if matched:
error(filename, linenum, 'whitespace/declaration', 3,
'Declaration has space between type name and %s in %s' %
(matched.group('pointer_operator'), matched.group(0).strip()))
...@@ -1183,6 +1183,7 @@ def CMDlint(change_info, args): ...@@ -1183,6 +1183,7 @@ def CMDlint(change_info, args):
""" """
try: try:
import cpplint import cpplint
import cpplint_chromium
except ImportError: except ImportError:
ErrorExit("You need to install cpplint.py to lint C++ files.") ErrorExit("You need to install cpplint.py to lint C++ files.")
# Change the current working directory before calling lint so that it # Change the current working directory before calling lint so that it
...@@ -1200,6 +1201,7 @@ def CMDlint(change_info, args): ...@@ -1200,6 +1201,7 @@ def CMDlint(change_info, args):
if not black_list: if not black_list:
black_list = DEFAULT_LINT_IGNORE_REGEX black_list = DEFAULT_LINT_IGNORE_REGEX
black_regex = re.compile(black_list) black_regex = re.compile(black_list)
extra_check_functions = [cpplint_chromium.CheckPointerDeclarationWhitespace]
# Access to a protected member _XX of a client class # Access to a protected member _XX of a client class
# pylint: disable=W0212 # pylint: disable=W0212
for filename in filenames: for filename in filenames:
...@@ -1207,7 +1209,8 @@ def CMDlint(change_info, args): ...@@ -1207,7 +1209,8 @@ def CMDlint(change_info, args):
if black_regex.match(filename): if black_regex.match(filename):
print "Ignoring file %s" % filename print "Ignoring file %s" % filename
else: else:
cpplint.ProcessFile(filename, cpplint._cpplint_state.verbose_level) cpplint.ProcessFile(filename, cpplint._cpplint_state.verbose_level,
extra_check_functions)
else: else:
print "Skipping file %s" % filename print "Skipping file %s" % filename
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment