Index: cpplint.py |
diff --git a/cpplint.py b/cpplint.py |
old mode 100755 |
new mode 100644 |
index 27def382726681d2a1254b1c6782a669482332f7..ac19165b357d187d392ad7fb59f3f014c7ba3535 |
--- a/cpplint.py |
+++ b/cpplint.py |
@@ -60,7 +60,7 @@ Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...] |
<file> [file] ... |
The style guidelines this tries to follow are those in |
- http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml |
+ https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml |
Every problem is given a confidence score from 1-5, with 5 meaning we are |
certain of the problem, and 1 meaning it could be a legitimate construct. |
@@ -177,6 +177,8 @@ Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...] |
_ERROR_CATEGORIES = [ |
'build/class', |
'build/c++11', |
+ 'build/c++14', |
+ 'build/c++tr1', |
'build/deprecated', |
'build/endif_comment', |
'build/explicit_make_pair', |
@@ -196,7 +198,6 @@ _ERROR_CATEGORIES = [ |
'readability/check', |
'readability/constructors', |
'readability/fn_size', |
- 'readability/function', |
'readability/inheritance', |
'readability/multiline_comment', |
'readability/multiline_string', |
@@ -227,6 +228,7 @@ _ERROR_CATEGORIES = [ |
'whitespace/comma', |
'whitespace/comments', |
'whitespace/empty_conditional_body', |
+ 'whitespace/empty_if_body', |
'whitespace/empty_loop_body', |
'whitespace/end_of_line', |
'whitespace/ending_newline', |
@@ -245,6 +247,7 @@ _ERROR_CATEGORIES = [ |
# compatibility they may still appear in NOLINT comments. |
_LEGACY_ERROR_CATEGORIES = [ |
'readability/streams', |
+ 'readability/function', |
] |
# The default state of the category filter. This is overridden by the --filter= |
@@ -253,6 +256,16 @@ _LEGACY_ERROR_CATEGORIES = [ |
# All entries here should start with a '-' or '+', as in the --filter= flag. |
_DEFAULT_FILTERS = ['-build/include_alpha'] |
+# The default list of categories suppressed for C (not C++) files. |
+_DEFAULT_C_SUPPRESSED_CATEGORIES = [ |
+ 'readability/casting', |
+ ] |
+ |
+# The default list of categories suppressed for Linux Kernel files. |
+_DEFAULT_KERNEL_SUPPRESSED_CATEGORIES = [ |
+ 'whitespace/tab', |
+ ] |
+ |
# 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 |
# hard-coded international strings, which belong in a separate i18n file. |
@@ -346,6 +359,7 @@ _CPP_HEADERS = frozenset([ |
'random', |
'ratio', |
'regex', |
+ 'scoped_allocator', |
'set', |
'sstream', |
'stack', |
@@ -393,6 +407,19 @@ _CPP_HEADERS = frozenset([ |
'cwctype', |
]) |
+# Type names |
+_TYPES = re.compile( |
+ r'^(?:' |
+ # [dcl.type.simple] |
+ r'(char(16_t|32_t)?)|wchar_t|' |
+ r'bool|short|int|long|signed|unsigned|float|double|' |
+ # [support.types] |
+ r'(ptrdiff_t|size_t|max_align_t|nullptr_t)|' |
+ # [cstdint.syn] |
+ r'(u?int(_fast|_least)?(8|16|32|64)_t)|' |
+ r'(u?int(max|ptr)_t)|' |
+ r')$') |
+ |
# These headers are excluded from [build/include] and [build/include_order] |
# checks: |
@@ -402,16 +429,18 @@ _CPP_HEADERS = frozenset([ |
_THIRD_PARTY_HEADERS_PATTERN = re.compile( |
r'^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$') |
+# Pattern for matching FileInfo.BaseName() against test file name |
+_TEST_FILE_SUFFIX = r'(_test|_unittest|_regtest)$' |
+ |
+# Pattern that matches only complete whitespace, possibly across multiple lines. |
+_EMPTY_CONDITIONAL_BODY_PATTERN = re.compile(r'^\s*$', re.DOTALL) |
# 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. |
+# testing/base/public/gunit.h. |
_CHECK_MACROS = [ |
'DCHECK', 'CHECK', |
- 'EXPECT_TRUE_M', 'EXPECT_TRUE', |
- 'ASSERT_TRUE_M', 'ASSERT_TRUE', |
- 'EXPECT_FALSE_M', 'EXPECT_FALSE', |
- 'ASSERT_FALSE_M', 'ASSERT_FALSE', |
+ 'EXPECT_TRUE', 'ASSERT_TRUE', |
+ 'EXPECT_FALSE', 'ASSERT_FALSE', |
] |
# Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE |
@@ -424,16 +453,12 @@ for op, replacement in [('==', 'EQ'), ('!=', 'NE'), |
_CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement |
_CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement |
_CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement |
- _CHECK_REPLACEMENT['EXPECT_TRUE_M'][op] = 'EXPECT_%s_M' % replacement |
- _CHECK_REPLACEMENT['ASSERT_TRUE_M'][op] = 'ASSERT_%s_M' % replacement |
for op, inv_replacement in [('==', 'NE'), ('!=', 'EQ'), |
('>=', 'LT'), ('>', 'LE'), |
('<=', 'GT'), ('<', 'GE')]: |
_CHECK_REPLACEMENT['EXPECT_FALSE'][op] = 'EXPECT_%s' % inv_replacement |
_CHECK_REPLACEMENT['ASSERT_FALSE'][op] = 'ASSERT_%s' % inv_replacement |
- _CHECK_REPLACEMENT['EXPECT_FALSE_M'][op] = 'EXPECT_%s_M' % inv_replacement |
- _CHECK_REPLACEMENT['ASSERT_FALSE_M'][op] = 'ASSERT_%s_M' % inv_replacement |
# Alternative tokens and their replacements. For full list, see section 2.5 |
# Alternative tokens [lex.digraph] in the C++ standard. |
@@ -482,6 +507,12 @@ _MATCH_ASM = re.compile(r'^\s*(?:asm|_asm|__asm|__asm__)' |
r'(?:\s+(volatile|__volatile__))?' |
r'\s*[{(]') |
+# Match strings that indicate we're working on a C (not C++) file. |
+_SEARCH_C_FILE = re.compile(r'\b(?:LINT_C_FILE|' |
+ r'vim?:\s*.*(\s*|:)filetype=c(\s*|:|$))') |
+ |
+# Match string that indicates we're working on a Linux Kernel file. |
+_SEARCH_KERNEL_FILE = re.compile(r'\b(?:LINT_KERNEL_FILE)') |
_regexp_compile_cache = {} |
@@ -501,8 +532,13 @@ _line_length = 80 |
# This is set by --extensions flag. |
_valid_extensions = set(['cc', 'h', 'cpp', 'cu', 'cuh']) |
+# {str, bool}: a map from error categories to booleans which indicate if the |
+# category should be suppressed for every line. |
+_global_error_suppressions = {} |
+ |
+ |
def ParseNolintSuppressions(filename, raw_line, linenum, error): |
- """Updates the global list of error-suppressions. |
+ """Updates the global list of line error-suppressions. |
Parses any NOLINT comments on the current line, updating the global |
error_suppressions store. Reports an error if the NOLINT comment |
@@ -533,24 +569,45 @@ def ParseNolintSuppressions(filename, raw_line, linenum, error): |
'Unknown NOLINT error category: %s' % category) |
+def ProcessGlobalSuppresions(lines): |
+ """Updates the list of global error suppressions. |
+ |
+ Parses any lint directives in the file that have global effect. |
+ |
+ Args: |
+ lines: An array of strings, each representing a line of the file, with the |
+ last element being empty if the file is terminated with a newline. |
+ """ |
+ for line in lines: |
+ if _SEARCH_C_FILE.search(line): |
+ for category in _DEFAULT_C_SUPPRESSED_CATEGORIES: |
+ _global_error_suppressions[category] = True |
+ if _SEARCH_KERNEL_FILE.search(line): |
+ for category in _DEFAULT_KERNEL_SUPPRESSED_CATEGORIES: |
+ _global_error_suppressions[category] = True |
+ |
+ |
def ResetNolintSuppressions(): |
"""Resets the set of NOLINT suppressions to empty.""" |
_error_suppressions.clear() |
+ _global_error_suppressions.clear() |
def IsErrorSuppressedByNolint(category, linenum): |
"""Returns true if the specified error category is suppressed on this line. |
Consults the global error_suppressions map populated by |
- ParseNolintSuppressions/ResetNolintSuppressions. |
+ ParseNolintSuppressions/ProcessGlobalSuppresions/ResetNolintSuppressions. |
Args: |
category: str, the category of the error. |
linenum: int, the current line number. |
Returns: |
- bool, True iff the error should be suppressed due to a NOLINT comment. |
+ bool, True iff the error should be suppressed due to a NOLINT comment or |
+ global suppression. |
""" |
- return (linenum in _error_suppressions.get(category, set()) or |
+ return (_global_error_suppressions.get(category, False) or |
+ linenum in _error_suppressions.get(category, set()) or |
linenum in _error_suppressions.get(None, set())) |
@@ -589,6 +646,11 @@ def Search(pattern, s): |
return _regexp_compile_cache[pattern].search(s) |
+def _IsSourceExtension(s): |
+ """File extension (excluding dot) matches a source file extension.""" |
+ return s in ('c', 'cc', 'cpp', 'cxx') |
+ |
+ |
class _IncludeState(object): |
"""Tracks line numbers for includes, and the order in which includes appear. |
@@ -944,6 +1006,9 @@ class _FunctionState(object): |
filename: The name of the current file. |
linenum: The number of the line to check. |
""" |
+ if not self.in_a_function: |
+ return |
+ |
if Match(r'T(EST|est)', self.current_function): |
base_trigger = self._TEST_TRIGGER |
else: |
@@ -1058,7 +1123,7 @@ class FileInfo(object): |
def IsSource(self): |
"""File has a source file extension.""" |
- return self.Extension()[1:] in ('c', 'cc', 'cpp', 'cxx') |
+ return _IsSourceExtension(self.Extension()[1:]) |
def _ShouldPrintError(category, confidence, linenum): |
@@ -1204,8 +1269,18 @@ def CleanseRawStrings(raw_lines): |
while delimiter is None: |
# 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: |
+ # |
+ # Once we have matched a raw string, we check the prefix of the |
+ # line to make sure that the line is not part of a single line |
+ # comment. It's done this way because we remove raw strings |
+ # before removing comments as opposed to removing comments |
+ # before removing raw strings. This is because there are some |
+ # cpplint checks that requires the comments to be preserved, but |
+ # we don't want to check comments that are inside raw strings. |
+ matched = Match(r'^(.*?)\b(?:R|u8R|uR|UR|LR)"([^\s\\()]*)\((.*)$', line) |
+ if (matched and |
+ not Match(r'^([^\'"]|\'(\\.|[^\'])*\'|"(\\.|[^"])*")*//', |
+ matched.group(1))): |
delimiter = ')' + matched.group(2) + '"' |
end = matched.group(3).find(delimiter) |
@@ -1776,11 +1851,11 @@ def CheckHeaderFileIncluded(filename, include_state, error): |
"""Logs an error if a .cc file does not include its header.""" |
# Do not check test files |
- if filename.endswith('_test.cc') or filename.endswith('_unittest.cc'): |
+ fileinfo = FileInfo(filename) |
+ if Search(_TEST_FILE_SUFFIX, fileinfo.BaseName()): |
return |
- fileinfo = FileInfo(filename) |
- headerfile = filename[0:len(filename) - 2] + 'h' |
+ headerfile = filename[0:len(filename) - len(fileinfo.Extension())] + '.h' |
if not os.path.exists(headerfile): |
return |
headername = FileInfo(headerfile).RepositoryName() |
@@ -1997,7 +2072,8 @@ def IsForwardClassDeclaration(clean_lines, linenum): |
class _BlockInfo(object): |
"""Stores information about a generic block of code.""" |
- def __init__(self, seen_open_brace): |
+ def __init__(self, linenum, seen_open_brace): |
+ self.starting_linenum = linenum |
self.seen_open_brace = seen_open_brace |
self.open_parentheses = 0 |
self.inline_asm = _NO_ASM |
@@ -2046,17 +2122,16 @@ class _BlockInfo(object): |
class _ExternCInfo(_BlockInfo): |
"""Stores information about an 'extern "C"' block.""" |
- def __init__(self): |
- _BlockInfo.__init__(self, True) |
+ def __init__(self, linenum): |
+ _BlockInfo.__init__(self, linenum, True) |
class _ClassInfo(_BlockInfo): |
"""Stores information about a class.""" |
def __init__(self, name, class_or_struct, clean_lines, linenum): |
- _BlockInfo.__init__(self, False) |
+ _BlockInfo.__init__(self, linenum, False) |
self.name = name |
- self.starting_linenum = linenum |
self.is_derived = False |
self.check_namespace_indentation = True |
if class_or_struct == 'struct': |
@@ -2124,9 +2199,8 @@ class _NamespaceInfo(_BlockInfo): |
"""Stores information about a namespace.""" |
def __init__(self, name, linenum): |
- _BlockInfo.__init__(self, False) |
+ _BlockInfo.__init__(self, linenum, False) |
self.name = name or '' |
- self.starting_linenum = linenum |
self.check_namespace_indentation = True |
def CheckEnd(self, filename, clean_lines, linenum, error): |
@@ -2145,7 +2219,7 @@ class _NamespaceInfo(_BlockInfo): |
# deciding what these nontrivial things are, so this check is |
# triggered by namespace size only, which works most of the time. |
if (linenum - self.starting_linenum < 10 |
- and not Match(r'};*\s*(//|/\*).*\bnamespace\b', line)): |
+ and not Match(r'^\s*};*\s*(//|/\*).*\bnamespace\b', line)): |
return |
# Look for matching comment at end of namespace. |
@@ -2162,18 +2236,18 @@ class _NamespaceInfo(_BlockInfo): |
# expected namespace. |
if self.name: |
# Named namespace |
- if not Match((r'};*\s*(//|/\*).*\bnamespace\s+' + re.escape(self.name) + |
- r'[\*/\.\\\s]*$'), |
+ if not Match((r'^\s*};*\s*(//|/\*).*\bnamespace\s+' + |
+ re.escape(self.name) + r'[\*/\.\\\s]*$'), |
line): |
error(filename, linenum, 'readability/namespace', 5, |
'Namespace should be terminated with "// namespace %s"' % |
self.name) |
else: |
# Anonymous namespace |
- if not Match(r'};*\s*(//|/\*).*\bnamespace[\*/\.\\\s]*$', line): |
+ if not Match(r'^\s*};*\s*(//|/\*).*\bnamespace[\*/\.\\\s]*$', line): |
# If "// namespace anonymous" or "// anonymous namespace (more text)", |
# mention "// anonymous namespace" as an acceptable form |
- if Match(r'}.*\b(namespace anonymous|anonymous namespace)\b', line): |
+ if Match(r'^\s*}.*\b(namespace anonymous|anonymous namespace)\b', line): |
error(filename, linenum, 'readability/namespace', 5, |
'Anonymous namespace should be terminated with "// namespace"' |
' or "// anonymous namespace"') |
@@ -2512,9 +2586,9 @@ class NestingState(object): |
if not self.SeenOpenBrace(): |
self.stack[-1].seen_open_brace = True |
elif Match(r'^extern\s*"[^"]*"\s*\{', line): |
- self.stack.append(_ExternCInfo()) |
+ self.stack.append(_ExternCInfo(linenum)) |
else: |
- self.stack.append(_BlockInfo(True)) |
+ self.stack.append(_BlockInfo(linenum, True)) |
if _MATCH_ASM.match(line): |
self.stack[-1].inline_asm = _BLOCK_ASM |
@@ -2626,7 +2700,8 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, |
r'\s+(register|static|extern|typedef)\b', |
line): |
error(filename, linenum, 'build/storage_class', 5, |
- 'Storage class (static, extern, typedef, etc) should be first.') |
+ 'Storage-class specifier (static, extern, typedef, etc) should be ' |
+ 'at the beginning of the declaration.') |
if Match(r'\s*#\s*endif\s*[^/\s]+', line): |
error(filename, linenum, 'build/endif_comment', 5, |
@@ -2665,9 +2740,7 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, |
base_classname = classinfo.name.split('::')[-1] |
# Look for single-argument constructors that aren't marked explicit. |
- # Technically a valid construct, but against style. Also look for |
- # non-single-argument constructors which are also technically valid, but |
- # strongly suggest something is wrong. |
+ # Technically a valid construct, but against style. |
explicit_constructor_match = Match( |
r'\s+(?:inline\s+)?(explicit\s+)?(?:inline\s+)?%s\s*' |
r'\(((?:[^()]|\([^()]*\))*)\)' |
@@ -2728,10 +2801,6 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, |
if noarg_constructor: |
error(filename, linenum, 'runtime/explicit', 5, |
'Zero-parameter constructors should not be marked explicit.') |
- else: |
- error(filename, linenum, 'runtime/explicit', 0, |
- 'Constructors that require multiple arguments ' |
- 'should not be marked explicit.') |
def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error): |
@@ -2786,6 +2855,7 @@ def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error): |
error(filename, linenum, 'whitespace/parens', 2, |
'Extra space after (') |
if (Search(r'\w\s+\(', fncall) and |
+ not Search(r'_{0,2}asm_{0,2}\s+_{0,2}volatile_{0,2}\s+\(', fncall) and |
not Search(r'#\s*define|typedef|using\s+\w+\s*=', fncall) and |
not Search(r'\w\s+\((\w+::)*\*\w+\)\(', fncall) and |
not Search(r'\bcase\s+\(', fncall)): |
@@ -2844,7 +2914,7 @@ def CheckForFunctionLengths(filename, clean_lines, linenum, |
"""Reports for long function bodies. |
For an overview why this is done, see: |
- http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Write_Short_Functions |
+ https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Write_Short_Functions |
Uses a simplistic algorithm assuming other style guidelines |
(especially spacing) are followed. |
@@ -2923,9 +2993,7 @@ def CheckComment(line, filename, linenum, next_line_start, 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=g-explicit-bool-comparison |
- if (line.count('"', 0, commentpos) - |
- line.count('\\"', 0, commentpos)) % 2 == 0: # not in quotes |
+ if re.sub(r'\\.', '', line[0:commentpos]).count('"') % 2 == 0: |
# Allow one space for new scopes, two spaces otherwise: |
if (not (Match(r'^.*{ *//', line) and next_line_start == commentpos) and |
((commentpos >= 1 and |
@@ -3174,8 +3242,8 @@ def CheckOperatorSpacing(filename, clean_lines, linenum, error): |
# macro context and don't do any checks. This avoids false |
# positives. |
# |
- # Note that && is not included here. Those are checked separately |
- # in CheckRValueReference |
+ # Note that && is not included here. This is because there are too |
+ # many false positives due to RValue references. |
match = Search(r'[^<>=!\s](==|!=|<=|>=|\|\|)[^<>=!\s,;\)]', line) |
if match: |
error(filename, linenum, 'whitespace/operators', 3, |
@@ -3209,7 +3277,7 @@ def CheckOperatorSpacing(filename, clean_lines, linenum, error): |
# |
# We also allow operators following an opening parenthesis, since |
# those tend to be macros that deal with operators. |
- match = Search(r'(operator|[^\s(<])(?:L|UL|ULL|l|ul|ull)?<<([^\s,=<])', line) |
+ match = Search(r'(operator|[^\s(<])(?:L|UL|LL|ULL|l|ul|ll|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, |
@@ -3313,22 +3381,90 @@ def CheckCommaSpacing(filename, clean_lines, linenum, error): |
'Missing space after ;') |
-def CheckBracesSpacing(filename, clean_lines, linenum, error): |
+def _IsType(clean_lines, nesting_state, expr): |
+ """Check if expression looks like a type name, returns true if so. |
+ |
+ Args: |
+ clean_lines: A CleansedLines instance containing the file. |
+ nesting_state: A NestingState instance which maintains information about |
+ the current stack of nested blocks being parsed. |
+ expr: The expression to check. |
+ Returns: |
+ True, if token looks like a type. |
+ """ |
+ # Keep only the last token in the expression |
+ last_word = Match(r'^.*(\b\S+)$', expr) |
+ if last_word: |
+ token = last_word.group(1) |
+ else: |
+ token = expr |
+ |
+ # Match native types and stdint types |
+ if _TYPES.match(token): |
+ return True |
+ |
+ # Try a bit harder to match templated types. Walk up the nesting |
+ # stack until we find something that resembles a typename |
+ # declaration for what we are looking for. |
+ typename_pattern = (r'\b(?:typename|class|struct)\s+' + re.escape(token) + |
+ r'\b') |
+ block_index = len(nesting_state.stack) - 1 |
+ while block_index >= 0: |
+ if isinstance(nesting_state.stack[block_index], _NamespaceInfo): |
+ return False |
+ |
+ # Found where the opening brace is. We want to scan from this |
+ # line up to the beginning of the function, minus a few lines. |
+ # template <typename Type1, // stop scanning here |
+ # ...> |
+ # class C |
+ # : public ... { // start scanning here |
+ last_line = nesting_state.stack[block_index].starting_linenum |
+ |
+ next_block_start = 0 |
+ if block_index > 0: |
+ next_block_start = nesting_state.stack[block_index - 1].starting_linenum |
+ first_line = last_line |
+ while first_line >= next_block_start: |
+ if clean_lines.elided[first_line].find('template') >= 0: |
+ break |
+ first_line -= 1 |
+ if first_line < next_block_start: |
+ # Didn't find any "template" keyword before reaching the next block, |
+ # there are probably no template things to check for this block |
+ block_index -= 1 |
+ continue |
+ |
+ # Look for typename in the specified range |
+ for i in xrange(first_line, last_line + 1, 1): |
+ if Search(typename_pattern, clean_lines.elided[i]): |
+ return True |
+ block_index -= 1 |
+ |
+ return False |
+ |
+ |
+def CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error): |
"""Checks for horizontal spacing near commas. |
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. |
""" |
line = clean_lines.elided[linenum] |
# Except after an opening paren, or after another opening brace (in case of |
# 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. |
+ # braces when they are delimiting blocks, classes, namespaces etc. |
+ # And since you should never have braces at the beginning of a line, |
+ # this is an easy test. Except that braces used for initialization don't |
+ # follow the same rule; we often don't want spaces before those. |
match = Match(r'^(.*[^ ({>]){', line) |
+ |
if match: |
# Try a bit harder to check for brace initialization. This |
# happens in one of the following forms: |
@@ -3358,6 +3494,7 @@ def CheckBracesSpacing(filename, clean_lines, linenum, error): |
# 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. |
+ leading_text = match.group(1) |
(endline, endlinenum, endpos) = CloseExpression( |
clean_lines, linenum, len(match.group(1))) |
trailing_text = '' |
@@ -3366,7 +3503,11 @@ def CheckBracesSpacing(filename, clean_lines, linenum, error): |
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): |
+ # We also suppress warnings for `uint64_t{expression}` etc., as the style |
+ # guide recommends brace initialization for integral types to avoid |
+ # overflow/truncation. |
+ if (not Match(r'^[\s}]*[{.;,)<>\]:]', trailing_text) |
+ and not _IsType(clean_lines, nesting_state, leading_text)): |
error(filename, linenum, 'whitespace/braces', 5, |
'Missing space before {') |
@@ -3410,405 +3551,6 @@ def IsDecltype(clean_lines, linenum, column): |
return False |
-def IsTemplateParameterList(clean_lines, linenum, column): |
- """Check if the token ending on (linenum, column) is the end of template<>. |
- |
- Args: |
- clean_lines: A CleansedLines instance containing the file. |
- linenum: the number of the line to check. |
- column: end column of the token to check. |
- Returns: |
- True if this token is end of a template parameter list, False otherwise. |
- """ |
- (_, startline, startpos) = ReverseCloseExpression( |
- clean_lines, linenum, column) |
- if (startpos > -1 and |
- Search(r'\btemplate\s*$', clean_lines.elided[startline][0:startpos])): |
- return True |
- return False |
- |
- |
-def IsRValueType(typenames, clean_lines, nesting_state, linenum, column): |
- """Check if the token ending on (linenum, column) is a type. |
- |
- Assumes that text to the right of the column is "&&" or a function |
- name. |
- |
- Args: |
- typenames: set of type names from template-argument-list. |
- clean_lines: A CleansedLines instance containing the file. |
- nesting_state: A NestingState instance which maintains information about |
- the current stack of nested blocks being parsed. |
- linenum: the number of the line to check. |
- column: end column of the token to check. |
- Returns: |
- True if this token is a type, False if we are not sure. |
- """ |
- prefix = clean_lines.elided[linenum][0:column] |
- |
- # Get one word to the left. If we failed to do so, this is most |
- # likely not a type, since it's unlikely that the type name and "&&" |
- # would be split across multiple lines. |
- match = Match(r'^(.*)(\b\w+|[>*)&])\s*$', prefix) |
- if not match: |
- return False |
- |
- # Check text following the token. If it's "&&>" or "&&," or "&&...", it's |
- # most likely a rvalue reference used inside a template. |
- suffix = clean_lines.elided[linenum][column:] |
- if Match(r'&&\s*(?:[>,]|\.\.\.)', suffix): |
- return True |
- |
- # Check for known types and end of templates: |
- # int&& variable |
- # vector<int>&& variable |
- # |
- # Because this function is called recursively, we also need to |
- # recognize pointer and reference types: |
- # int* Function() |
- # int& Function() |
- if (match.group(2) in typenames or |
- match.group(2) in ['char', 'char16_t', 'char32_t', 'wchar_t', 'bool', |
- 'short', 'int', 'long', 'signed', 'unsigned', |
- 'float', 'double', 'void', 'auto', '>', '*', '&']): |
- return True |
- |
- # If we see a close parenthesis, look for decltype on the other side. |
- # decltype would unambiguously identify a type, anything else is |
- # probably a parenthesized expression and not a type. |
- if match.group(2) == ')': |
- return IsDecltype( |
- clean_lines, linenum, len(match.group(1)) + len(match.group(2)) - 1) |
- |
- # Check for casts and cv-qualifiers. |
- # match.group(1) remainder |
- # -------------- --------- |
- # const_cast< type&& |
- # const type&& |
- # type const&& |
- if Search(r'\b(?:const_cast\s*<|static_cast\s*<|dynamic_cast\s*<|' |
- r'reinterpret_cast\s*<|\w+\s)\s*$', |
- match.group(1)): |
- return True |
- |
- # Look for a preceding symbol that might help differentiate the context. |
- # These are the cases that would be ambiguous: |
- # match.group(1) remainder |
- # -------------- --------- |
- # Call ( expression && |
- # Declaration ( type&& |
- # sizeof ( type&& |
- # if ( expression && |
- # while ( expression && |
- # for ( type&& |
- # for( ; expression && |
- # statement ; type&& |
- # block { type&& |
- # constructor { expression && |
- start = linenum |
- line = match.group(1) |
- match_symbol = None |
- while start >= 0: |
- # We want to skip over identifiers and commas to get to a symbol. |
- # Commas are skipped so that we can find the opening parenthesis |
- # for function parameter lists. |
- match_symbol = Match(r'^(.*)([^\w\s,])[\w\s,]*$', line) |
- if match_symbol: |
- break |
- start -= 1 |
- line = clean_lines.elided[start] |
- |
- if not match_symbol: |
- # Probably the first statement in the file is an rvalue reference |
- return True |
- |
- if match_symbol.group(2) == '}': |
- # Found closing brace, probably an indicate of this: |
- # block{} type&& |
- return True |
- |
- if match_symbol.group(2) == ';': |
- # Found semicolon, probably one of these: |
- # for(; expression && |
- # statement; type&& |
- |
- # Look for the previous 'for(' in the previous lines. |
- before_text = match_symbol.group(1) |
- for i in xrange(start - 1, max(start - 6, 0), -1): |
- before_text = clean_lines.elided[i] + before_text |
- if Search(r'for\s*\([^{};]*$', before_text): |
- # This is the condition inside a for-loop |
- return False |
- |
- # Did not find a for-init-statement before this semicolon, so this |
- # is probably a new statement and not a condition. |
- return True |
- |
- if match_symbol.group(2) == '{': |
- # Found opening brace, probably one of these: |
- # block{ type&& = ... ; } |
- # constructor{ expression && expression } |
- |
- # Look for a closing brace or a semicolon. If we see a semicolon |
- # first, this is probably a rvalue reference. |
- line = clean_lines.elided[start][0:len(match_symbol.group(1)) + 1] |
- end = start |
- depth = 1 |
- while True: |
- for ch in line: |
- if ch == ';': |
- return True |
- elif ch == '{': |
- depth += 1 |
- elif ch == '}': |
- depth -= 1 |
- if depth == 0: |
- return False |
- end += 1 |
- if end >= clean_lines.NumLines(): |
- break |
- line = clean_lines.elided[end] |
- # Incomplete program? |
- return False |
- |
- if match_symbol.group(2) == '(': |
- # Opening parenthesis. Need to check what's to the left of the |
- # parenthesis. Look back one extra line for additional context. |
- before_text = match_symbol.group(1) |
- if linenum > 1: |
- before_text = clean_lines.elided[linenum - 1] + before_text |
- before_text = match_symbol.group(1) |
- |
- # Patterns that are likely to be types: |
- # [](type&& |
- # for (type&& |
- # sizeof(type&& |
- # operator=(type&& |
- # |
- if Search(r'(?:\]|\bfor|\bsizeof|\boperator\s*\S+\s*)\s*$', before_text): |
- return True |
- |
- # Patterns that are likely to be expressions: |
- # if (expression && |
- # while (expression && |
- # : initializer(expression && |
- # , initializer(expression && |
- # ( FunctionCall(expression && |
- # + FunctionCall(expression && |
- # + (expression && |
- # |
- # The last '+' represents operators such as '+' and '-'. |
- if Search(r'(?:\bif|\bwhile|[-+=%^(<!?:,&*]\s*)$', before_text): |
- return False |
- |
- # Something else. Check that tokens to the left look like |
- # return_type function_name |
- match_func = Match(r'^(.*\S.*)\s+\w(?:\w|::)*(?:<[^<>]*>)?\s*$', |
- match_symbol.group(1)) |
- if match_func: |
- # Check for constructors, which don't have return types. |
- if Search(r'\b(?:explicit|inline)$', match_func.group(1)): |
- return True |
- implicit_constructor = Match(r'\s*(\w+)\((?:const\s+)?(\w+)', prefix) |
- if (implicit_constructor and |
- implicit_constructor.group(1) == implicit_constructor.group(2)): |
- return True |
- return IsRValueType(typenames, clean_lines, nesting_state, linenum, |
- len(match_func.group(1))) |
- |
- # Nothing before the function name. If this is inside a block scope, |
- # this is probably a function call. |
- return not (nesting_state.previous_stack_top and |
- nesting_state.previous_stack_top.IsBlockInfo()) |
- |
- if match_symbol.group(2) == '>': |
- # Possibly a closing bracket, check that what's on the other side |
- # looks like the start of a template. |
- return IsTemplateParameterList( |
- clean_lines, start, len(match_symbol.group(1))) |
- |
- # Some other symbol, usually something like "a=b&&c". This is most |
- # likely not a type. |
- return False |
- |
- |
-def IsDeletedOrDefault(clean_lines, linenum): |
- """Check if current constructor or operator is deleted or default. |
- |
- Args: |
- clean_lines: A CleansedLines instance containing the file. |
- linenum: The number of the line to check. |
- Returns: |
- True if this is a deleted or default constructor. |
- """ |
- open_paren = clean_lines.elided[linenum].find('(') |
- if open_paren < 0: |
- return False |
- (close_line, _, close_paren) = CloseExpression( |
- clean_lines, linenum, open_paren) |
- if close_paren < 0: |
- return False |
- return Match(r'\s*=\s*(?:delete|default)\b', close_line[close_paren:]) |
- |
- |
-def IsRValueAllowed(clean_lines, linenum, typenames): |
- """Check if RValue reference is allowed on a particular line. |
- |
- Args: |
- clean_lines: A CleansedLines instance containing the file. |
- linenum: The number of the line to check. |
- typenames: set of type names from template-argument-list. |
- Returns: |
- True if line is within the region where RValue references are allowed. |
- """ |
- # Allow region marked by PUSH/POP macros |
- for i in xrange(linenum, 0, -1): |
- line = clean_lines.elided[i] |
- if Match(r'GOOGLE_ALLOW_RVALUE_REFERENCES_(?:PUSH|POP)', line): |
- if not line.endswith('PUSH'): |
- return False |
- for j in xrange(linenum, clean_lines.NumLines(), 1): |
- line = clean_lines.elided[j] |
- if Match(r'GOOGLE_ALLOW_RVALUE_REFERENCES_(?:PUSH|POP)', line): |
- return line.endswith('POP') |
- |
- # Allow operator= |
- line = clean_lines.elided[linenum] |
- if Search(r'\boperator\s*=\s*\(', line): |
- return IsDeletedOrDefault(clean_lines, linenum) |
- |
- # Allow constructors |
- match = Match(r'\s*(?:[\w<>]+::)*([\w<>]+)\s*::\s*([\w<>]+)\s*\(', line) |
- if match and match.group(1) == match.group(2): |
- return IsDeletedOrDefault(clean_lines, linenum) |
- if Search(r'\b(?:explicit|inline)\s+[\w<>]+\s*\(', line): |
- return IsDeletedOrDefault(clean_lines, linenum) |
- |
- if Match(r'\s*[\w<>]+\s*\(', line): |
- previous_line = 'ReturnType' |
- if linenum > 0: |
- previous_line = clean_lines.elided[linenum - 1] |
- if Match(r'^\s*$', previous_line) or Search(r'[{}:;]\s*$', previous_line): |
- return IsDeletedOrDefault(clean_lines, linenum) |
- |
- # Reject types not mentioned in template-argument-list |
- while line: |
- match = Match(r'^.*?(\w+)\s*&&(.*)$', line) |
- if not match: |
- break |
- if match.group(1) not in typenames: |
- return False |
- line = match.group(2) |
- |
- # All RValue types that were in template-argument-list should have |
- # been removed by now. Those were allowed, assuming that they will |
- # be forwarded. |
- # |
- # If there are no remaining RValue types left (i.e. types that were |
- # not found in template-argument-list), flag those as not allowed. |
- return line.find('&&') < 0 |
- |
- |
-def GetTemplateArgs(clean_lines, linenum): |
- """Find list of template arguments associated with this function declaration. |
- |
- Args: |
- clean_lines: A CleansedLines instance containing the file. |
- linenum: Line number containing the start of the function declaration, |
- usually one line after the end of the template-argument-list. |
- Returns: |
- Set of type names, or empty set if this does not appear to have |
- any template parameters. |
- """ |
- # Find start of function |
- func_line = linenum |
- while func_line > 0: |
- line = clean_lines.elided[func_line] |
- if Match(r'^\s*$', line): |
- return set() |
- if line.find('(') >= 0: |
- break |
- func_line -= 1 |
- if func_line == 0: |
- return set() |
- |
- # Collapse template-argument-list into a single string |
- argument_list = '' |
- match = Match(r'^(\s*template\s*)<', clean_lines.elided[func_line]) |
- if match: |
- # template-argument-list on the same line as function name |
- start_col = len(match.group(1)) |
- _, end_line, end_col = CloseExpression(clean_lines, func_line, start_col) |
- if end_col > -1 and end_line == func_line: |
- start_col += 1 # Skip the opening bracket |
- argument_list = clean_lines.elided[func_line][start_col:end_col] |
- |
- elif func_line > 1: |
- # template-argument-list one line before function name |
- match = Match(r'^(.*)>\s*$', clean_lines.elided[func_line - 1]) |
- if match: |
- end_col = len(match.group(1)) |
- _, start_line, start_col = ReverseCloseExpression( |
- clean_lines, func_line - 1, end_col) |
- if start_col > -1: |
- start_col += 1 # Skip the opening bracket |
- while start_line < func_line - 1: |
- argument_list += clean_lines.elided[start_line][start_col:] |
- start_col = 0 |
- start_line += 1 |
- argument_list += clean_lines.elided[func_line - 1][start_col:end_col] |
- |
- if not argument_list: |
- return set() |
- |
- # Extract type names |
- typenames = set() |
- while True: |
- match = Match(r'^[,\s]*(?:typename|class)(?:\.\.\.)?\s+(\w+)(.*)$', |
- argument_list) |
- if not match: |
- break |
- typenames.add(match.group(1)) |
- argument_list = match.group(2) |
- return typenames |
- |
- |
-def CheckRValueReference(filename, clean_lines, linenum, nesting_state, error): |
- """Check for rvalue references. |
- |
- 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. |
- """ |
- # Find lines missing spaces around &&. |
- # TODO(unknown): currently we don't check for rvalue references |
- # with spaces surrounding the && to avoid false positives with |
- # boolean expressions. |
- line = clean_lines.elided[linenum] |
- match = Match(r'^(.*\S)&&', line) |
- if not match: |
- match = Match(r'(.*)&&\S', line) |
- if (not match) or '(&&)' in line or Search(r'\boperator\s*$', match.group(1)): |
- return |
- |
- # Either poorly formed && or an rvalue reference, check the context |
- # to get a more accurate error message. Mostly we want to determine |
- # if what's to the left of "&&" is a type or not. |
- typenames = GetTemplateArgs(clean_lines, linenum) |
- and_pos = len(match.group(1)) |
- if IsRValueType(typenames, clean_lines, nesting_state, linenum, and_pos): |
- if not IsRValueAllowed(clean_lines, linenum, typenames): |
- error(filename, linenum, 'build/c++11', 3, |
- 'RValue references are an unapproved C++ feature.') |
- else: |
- error(filename, linenum, 'whitespace/operators', 3, |
- 'Missing spaces around &&') |
- |
- |
def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error): |
"""Checks for additional blank line issues related to sections. |
@@ -3906,10 +3648,13 @@ def CheckBraces(filename, clean_lines, linenum, error): |
# 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. |
+ # previous line starts a preprocessor block. We also allow a brace on the |
+ # following line if it is part of an array initialization and would not fit |
+ # within the 80 character limit of the preceding line. |
prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] |
if (not Search(r'[,;:}{(]\s*$', prevline) and |
- not Match(r'\s*#', prevline)): |
+ not Match(r'\s*#', prevline) and |
+ not (GetLineWidth(prevline) > _line_length - 2 and '[]' in prevline)): |
error(filename, linenum, 'whitespace/braces', 4, |
'{ should almost always be at the end of the previous line') |
@@ -4085,13 +3830,14 @@ def CheckTrailingSemicolon(filename, clean_lines, linenum, error): |
# In addition to macros, we also don't want to warn on |
# - Compound literals |
# - Lambdas |
- # - alignas specifier with anonymous structs: |
+ # - alignas specifier with anonymous structs |
+ # - decltype |
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) |
+ macro = Search(r'\b([A-Z_][A-Z0-9_]*)\s*$', line_prefix) |
func = Match(r'^(.*\])\s*$', line_prefix) |
if ((macro and |
macro.group(1) not in ( |
@@ -4100,6 +3846,7 @@ def CheckTrailingSemicolon(filename, clean_lines, linenum, error): |
'LOCKS_EXCLUDED', 'INTERFACE_DEF')) or |
(func and not Search(r'\boperator\s*\[\s*\]', func.group(1))) or |
Search(r'\b(?:struct|union)\s+alignas\s*$', line_prefix) or |
+ Search(r'\bdecltype$', line_prefix) or |
Search(r'\s+=\s*$', line_prefix)): |
match = None |
if (match and |
@@ -4159,7 +3906,7 @@ def CheckEmptyBlockBody(filename, clean_lines, linenum, error): |
line = clean_lines.elided[linenum] |
matched = Match(r'\s*(for|while|if)\s*\(', line) |
if matched: |
- # Find the end of the conditional expression |
+ # Find the end of the conditional expression. |
(end_line, end_linenum, end_pos) = CloseExpression( |
clean_lines, linenum, line.find('(')) |
@@ -4174,6 +3921,75 @@ def CheckEmptyBlockBody(filename, clean_lines, linenum, error): |
error(filename, end_linenum, 'whitespace/empty_loop_body', 5, |
'Empty loop bodies should use {} or continue') |
+ # Check for if statements that have completely empty bodies (no comments) |
+ # and no else clauses. |
+ if end_pos >= 0 and matched.group(1) == 'if': |
+ # Find the position of the opening { for the if statement. |
+ # Return without logging an error if it has no brackets. |
+ opening_linenum = end_linenum |
+ opening_line_fragment = end_line[end_pos:] |
+ # Loop until EOF or find anything that's not whitespace or opening {. |
+ while not Search(r'^\s*\{', opening_line_fragment): |
+ if Search(r'^(?!\s*$)', opening_line_fragment): |
+ # Conditional has no brackets. |
+ return |
+ opening_linenum += 1 |
+ if opening_linenum == len(clean_lines.elided): |
+ # Couldn't find conditional's opening { or any code before EOF. |
+ return |
+ opening_line_fragment = clean_lines.elided[opening_linenum] |
+ # Set opening_line (opening_line_fragment may not be entire opening line). |
+ opening_line = clean_lines.elided[opening_linenum] |
+ |
+ # Find the position of the closing }. |
+ opening_pos = opening_line_fragment.find('{') |
+ if opening_linenum == end_linenum: |
+ # We need to make opening_pos relative to the start of the entire line. |
+ opening_pos += end_pos |
+ (closing_line, closing_linenum, closing_pos) = CloseExpression( |
+ clean_lines, opening_linenum, opening_pos) |
+ if closing_pos < 0: |
+ return |
+ |
+ # Now construct the body of the conditional. This consists of the portion |
+ # of the opening line after the {, all lines until the closing line, |
+ # and the portion of the closing line before the }. |
+ if (clean_lines.raw_lines[opening_linenum] != |
+ CleanseComments(clean_lines.raw_lines[opening_linenum])): |
+ # Opening line ends with a comment, so conditional isn't empty. |
+ return |
+ if closing_linenum > opening_linenum: |
+ # Opening line after the {. Ignore comments here since we checked above. |
+ body = list(opening_line[opening_pos+1:]) |
+ # All lines until closing line, excluding closing line, with comments. |
+ body.extend(clean_lines.raw_lines[opening_linenum+1:closing_linenum]) |
+ # Closing line before the }. Won't (and can't) have comments. |
+ body.append(clean_lines.elided[closing_linenum][:closing_pos-1]) |
+ body = '\n'.join(body) |
+ else: |
+ # If statement has brackets and fits on a single line. |
+ body = opening_line[opening_pos+1:closing_pos-1] |
+ |
+ # Check if the body is empty |
+ if not _EMPTY_CONDITIONAL_BODY_PATTERN.search(body): |
+ return |
+ # The body is empty. Now make sure there's not an else clause. |
+ current_linenum = closing_linenum |
+ current_line_fragment = closing_line[closing_pos:] |
+ # Loop until EOF or find anything that's not whitespace or else clause. |
+ while Search(r'^\s*$|^(?=\s*else)', current_line_fragment): |
+ if Search(r'^(?=\s*else)', current_line_fragment): |
+ # Found an else clause, so don't log an error. |
+ return |
+ current_linenum += 1 |
+ if current_linenum == len(clean_lines.elided): |
+ break |
+ current_line_fragment = clean_lines.elided[current_linenum] |
+ |
+ # The body is empty and there's no else clause until EOF or other code. |
+ error(filename, end_linenum, 'whitespace/empty_if_body', 4, |
+ ('If statement had no body and no else clause')) |
+ |
def FindCheckMacro(line): |
"""Find a replaceable CHECK-like macro. |
@@ -4393,6 +4209,7 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, |
# raw strings, |
raw_lines = clean_lines.lines_without_raw_strings |
line = raw_lines[linenum] |
+ prev = raw_lines[linenum - 1] if linenum > 0 else '' |
if line.find('\t') != -1: |
error(filename, linenum, 'whitespace/tab', 1, |
@@ -4416,19 +4233,24 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, |
cleansed_line = clean_lines.elided[linenum] |
while initial_spaces < len(line) and line[initial_spaces] == ' ': |
initial_spaces += 1 |
- 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 |
# section labels, and also lines containing multi-line raw strings. |
- elif ((initial_spaces == 1 or initial_spaces == 3) and |
- not Match(scope_or_label_pattern, cleansed_line) and |
- not (clean_lines.raw_lines[linenum] != line and |
- Match(r'^\s*""', line))): |
+ # We also don't check for lines that look like continuation lines |
+ # (of lines ending in double quotes, commas, equals, or angle brackets) |
+ # because the rules for how to indent those are non-trivial. |
+ if (not Search(r'[",=><] *$', prev) and |
+ (initial_spaces == 1 or initial_spaces == 3) and |
+ not Match(scope_or_label_pattern, cleansed_line) and |
+ not (clean_lines.raw_lines[linenum] != line and |
+ Match(r'^\s*""', line))): |
error(filename, linenum, 'whitespace/indent', 3, |
'Weird number of spaces at line-start. ' |
'Are you using a 2-space indent?') |
+ if line and line[-1].isspace(): |
+ error(filename, linenum, 'whitespace/end_of_line', 4, |
+ 'Line ends in whitespace. Consider deleting these extra spaces.') |
+ |
# Check if the line is a header guard. |
is_header_guard = False |
if file_extension == 'h': |
@@ -4447,14 +4269,10 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, |
# developers fault. |
if (not line.startswith('#include') and not is_header_guard and |
not Match(r'^\s*//.*http(s?)://\S*$', line) and |
+ not Match(r'^\s*//\s*[^\s]*$', line) and |
not Match(r'^// \$Id:.*#[0-9]+ \$$', line)): |
line_width = GetLineWidth(line) |
- 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 %i characters' % |
- extended_length) |
- elif line_width > _line_length: |
+ if line_width > _line_length: |
error(filename, linenum, 'whitespace/line_length', 2, |
'Lines should be <= %i characters long' % _line_length) |
@@ -4479,9 +4297,8 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, |
CheckOperatorSpacing(filename, clean_lines, linenum, error) |
CheckParenthesisSpacing(filename, clean_lines, linenum, error) |
CheckCommaSpacing(filename, clean_lines, linenum, error) |
- CheckBracesSpacing(filename, clean_lines, linenum, error) |
+ CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error) |
CheckSpacingForFunctionCall(filename, clean_lines, linenum, error) |
- CheckRValueReference(filename, clean_lines, linenum, nesting_state, error) |
CheckCheck(filename, clean_lines, linenum, error) |
CheckAltTokens(filename, clean_lines, linenum, error) |
classinfo = nesting_state.InnermostClass() |
@@ -4525,23 +4342,6 @@ def _DropCommonSuffixes(filename): |
return os.path.splitext(filename)[0] |
-def _IsTestFilename(filename): |
- """Determines if the given filename has a suffix that identifies it as a test. |
- |
- Args: |
- filename: The input filename. |
- |
- Returns: |
- True if 'filename' looks like a test, False otherwise. |
- """ |
- if (filename.endswith('_test.cc') or |
- filename.endswith('_unittest.cc') or |
- filename.endswith('_regtest.cc')): |
- return True |
- else: |
- return False |
- |
- |
def _ClassifyInclude(fileinfo, include, is_system): |
"""Figures out what kind of header 'include' is. |
@@ -4756,6 +4556,9 @@ _RE_PATTERN_REF_PARAM = re.compile( |
_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')') |
+# Stream types. |
+_RE_PATTERN_REF_STREAM_PARAM = ( |
+ r'(?:.*stream\s*&\s*' + _RE_PATTERN_IDENT + r')') |
def CheckLanguage(filename, clean_lines, linenum, file_extension, |
@@ -4912,7 +4715,7 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, |
and line[-1] != '\\'): |
error(filename, linenum, 'build/namespaces', 4, |
'Do not use unnamed namespaces in header files. See ' |
- 'http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces' |
+ 'https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces' |
' for more information.') |
@@ -4933,9 +4736,13 @@ def CheckGlobalStatic(filename, clean_lines, linenum, error): |
# 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. |
+ # globals with constructors are initialized before the first access, and |
+ # also because globals can be destroyed when some threads are still running. |
+ # TODO(unknown): Generalize this to also find static unique_ptr instances. |
+ # TODO(unknown): File bugs for clang-tidy to find these. |
match = Match( |
- r'((?:|static +)(?:|const +))string +([a-zA-Z0-9_:]+)\b(.*)', |
+ r'((?:|static +)(?:|const +))(?::*std::)?string( +const)? +' |
+ r'([a-zA-Z0-9_:]+)\b(.*)', |
line) |
# Remove false positives: |
@@ -4955,15 +4762,20 @@ def CheckGlobalStatic(filename, clean_lines, linenum, error): |
# matching identifiers. |
# string Class::operator*() |
if (match and |
- not Search(r'\bstring\b(\s+const)?\s*\*\s*(const\s+)?\w', line) and |
+ not Search(r'\bstring\b(\s+const)?\s*[\*\&]\s*(const\s+)?\w', line) 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))) |
+ not Match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)*\s*\(([^"]|$)', match.group(4))): |
+ if Search(r'\bconst\b', line): |
+ error(filename, linenum, 'runtime/string', 4, |
+ 'For a static/global string constant, use a C style string ' |
+ 'instead: "%schar%s %s[]".' % |
+ (match.group(1), match.group(2) or '', match.group(3))) |
+ else: |
+ error(filename, linenum, 'runtime/string', 4, |
+ 'Static/global string variables are not permitted.') |
- if Search(r'\b([A-Za-z0-9_]*_)\(\1\)', line): |
+ if (Search(r'\b([A-Za-z0-9_]*_)\(\1\)', line) or |
+ Search(r'\b([A-Za-z0-9_]*_)\(CHECK_NOTNULL\(\1\)\)', line)): |
error(filename, linenum, 'runtime/init', 4, |
'You seem to be initializing a member variable with itself.') |
@@ -5208,7 +5020,8 @@ def CheckForNonConstReference(filename, clean_lines, linenum, |
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): |
+ if (not Match(_RE_PATTERN_CONST_REF_PARAM, parameter) and |
+ not Match(_RE_PATTERN_REF_STREAM_PARAM, parameter)): |
error(filename, linenum, 'runtime/references', 2, |
'Is this a non-const reference? ' |
'If so, make const or use a pointer: ' + |
@@ -5231,7 +5044,7 @@ def CheckCasts(filename, clean_lines, linenum, error): |
# Parameterless conversion functions, such as bool(), are allowed as they are |
# probably a member operator declaration or default constructor. |
match = Search( |
- r'(\bnew\s+|\S<\s*(?:const\s+)?)?\b' |
+ r'(\bnew\s+(?:const\s+)?|\S<\s*(?:const\s+)?)?\b' |
r'(int|float|double|bool|char|int32|uint32|int64|uint64)' |
r'(\([^)].*)', line) |
expecting_function = ExpectingFunctionArgs(clean_lines, linenum) |
@@ -5372,63 +5185,12 @@ def CheckCStyleCast(filename, clean_lines, linenum, cast_type, pattern, error): |
if context.endswith(' operator++') or context.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; |
- # [](int) -> bool { |
- # |
- # 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)) |
- # Function((function_pointer_arg)(int), int param) |
- # <TemplateArgument(int)>; |
- # <(FunctionPointerTemplateArgument)(int)>; |
+ # 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. |
remainder = line[match.end(0):] |
if Match(r'^\s*(?:;|const\b|throw\b|final\b|override\b|[=>{),]|->)', |
remainder): |
- # Looks like an unnamed parameter. |
- |
- # 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*/); |
- raw_line = clean_lines.raw_lines[linenum] |
- 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 |
+ return False |
# At this point, all that should be left is actual casts. |
error(filename, linenum, 'readability/casting', 4, |
@@ -5557,13 +5319,13 @@ def FilesBelongToSameModule(filename_cc, filename_h): |
string: the additional prefix needed to open the header file. |
""" |
- if not filename_cc.endswith('.cc'): |
+ fileinfo = FileInfo(filename_cc) |
+ if not fileinfo.IsSource(): |
return (False, '') |
- filename_cc = filename_cc[:-len('.cc')] |
- if filename_cc.endswith('_unittest'): |
- filename_cc = filename_cc[:-len('_unittest')] |
- elif filename_cc.endswith('_test'): |
- filename_cc = filename_cc[:-len('_test')] |
+ filename_cc = filename_cc[:-len(fileinfo.Extension())] |
+ matched_test_suffix = Search(_TEST_FILE_SUFFIX, fileinfo.BaseName()) |
+ if matched_test_suffix: |
+ filename_cc = filename_cc[:-len(matched_test_suffix.group(1))] |
filename_cc = filename_cc.replace('/public/', '/') |
filename_cc = filename_cc.replace('/internal/', '/') |
@@ -5727,31 +5489,6 @@ def CheckMakePairUsesDeduction(filename, clean_lines, linenum, error): |
' OR use pair directly OR if appropriate, construct a pair directly') |
-def CheckDefaultLambdaCaptures(filename, clean_lines, linenum, error): |
- """Check that default lambda captures are not used. |
- |
- 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] |
- |
- # A lambda introducer specifies a default capture if it starts with "[=" |
- # or if it starts with "[&" _not_ followed by an identifier. |
- match = Match(r'^(.*)\[\s*(?:=|&[^\w])', line) |
- if match: |
- # Found a potential error, check what comes after the lambda-introducer. |
- # If it's not open parenthesis (for lambda-declarator) or open brace |
- # (for compound-statement), it's not a lambda. |
- line, _, pos = CloseExpression(clean_lines, linenum, len(match.group(1))) |
- if pos >= 0 and Match(r'^\s*[{(]', line[pos:]): |
- error(filename, linenum, 'build/c++11', |
- 4, # 4 = high confidence |
- 'Default lambda captures are an unapproved C++ feature.') |
- |
- |
def CheckRedundantVirtual(filename, clean_lines, linenum, error): |
"""Check if line contains a redundant "virtual" function-specifier. |
@@ -5950,7 +5687,6 @@ def ProcessLine(filename, file_extension, clean_lines, line, |
CheckPosixThreading(filename, clean_lines, line, error) |
CheckInvalidIncrement(filename, clean_lines, line, error) |
CheckMakePairUsesDeduction(filename, clean_lines, line, error) |
- CheckDefaultLambdaCaptures(filename, clean_lines, line, error) |
CheckRedundantVirtual(filename, clean_lines, line, error) |
CheckRedundantOverrideOrFinal(filename, clean_lines, line, error) |
for check_fn in extra_check_functions: |
@@ -5967,8 +5703,14 @@ def FlagCxx11Features(filename, clean_lines, linenum, error): |
""" |
line = clean_lines.elided[linenum] |
- # Flag unapproved C++11 headers. |
include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) |
+ |
+ # Flag unapproved C++ TR1 headers. |
+ if include and include.group(1).startswith('tr1/'): |
+ error(filename, linenum, 'build/c++tr1', 5, |
+ ('C++ TR1 headers such as <%s> are unapproved.') % include.group(1)) |
+ |
+ # Flag unapproved C++11 headers. |
if include and include.group(1) in ('cfenv', |
'condition_variable', |
'fenv.h', |
@@ -6002,6 +5744,25 @@ def FlagCxx11Features(filename, clean_lines, linenum, error): |
'they may let you use it.') % top_name) |
+def FlagCxx14Features(filename, clean_lines, linenum, error): |
+ """Flag those C++14 features that we restrict. |
+ |
+ 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] |
+ |
+ include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) |
+ |
+ # Flag unapproved C++14 headers. |
+ if include and include.group(1) in ('scoped_allocator', 'shared_mutex'): |
+ error(filename, linenum, 'build/c++14', 5, |
+ ('<%s> is an unapproved C++14 header.') % include.group(1)) |
+ |
+ |
def ProcessFileData(filename, file_extension, lines, error, |
extra_check_functions=[]): |
"""Performs lint checks and reports any errors to the given error function. |
@@ -6027,7 +5788,7 @@ def ProcessFileData(filename, file_extension, lines, error, |
ResetNolintSuppressions() |
CheckForCopyright(filename, lines, error) |
- |
+ ProcessGlobalSuppresions(lines) |
RemoveMultiLineComments(filename, lines, error) |
clean_lines = CleansedLines(lines) |
@@ -6044,7 +5805,7 @@ def ProcessFileData(filename, file_extension, lines, error, |
CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) |
# Check that the .cc file has included its header if it exists. |
- if file_extension == 'cc': |
+ if _IsSourceExtension(file_extension): |
CheckHeaderFileIncluded(filename, include_state, error) |
# We check here rather than inside ProcessLine so that we see raw |