Index: cpplint.py |
=================================================================== |
--- cpplint.py (revision 32179) |
+++ cpplint.py (working copy) |
@@ -1,14 +1,32 @@ |
#!/usr/bin/python2.4 |
# |
-# cpplint.py is Copyright (C) 2009 Google Inc. |
+# Copyright (c) 2009 Google Inc. All rights reserved. |
# |
-# It is free software; you can redistribute it and/or modify it under the |
-# terms of either: |
+# Redistribution and use in source and binary forms, with or without |
+# modification, are permitted provided that the following conditions are |
+# met: |
# |
-# a) the GNU General Public License as published by the Free Software |
-# Foundation; either version 1, or (at your option) any later version, or |
+# * 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. |
# |
-# b) the "Artistic License". |
+# 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. |
# Here are some issues that I've had people identify in my code during reviews, |
# that I think are possible to flag automatically in a lint tool. If these were |
@@ -74,6 +92,7 @@ |
_USAGE = """ |
Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...] |
+ [--counting=total|toplevel|detailed] |
<file> [file] ... |
The style guidelines this tries to follow are those in |
@@ -112,6 +131,13 @@ |
To see a list of all the categories used in cpplint, pass no arg: |
--filter= |
+ |
+ counting=total|toplevel|detailed |
+ The total number of errors found is always printed. If |
+ 'toplevel' is provided, then the count of errors in each of |
+ the top-level categories like 'build' and 'whitespace' will |
+ also be printed. If 'detailed' is provided, then a count |
+ is provided for each category like 'build/class'. |
""" |
# We categorize each error message we print. Here are the categories. |
@@ -126,6 +152,7 @@ |
build/forward_decl |
build/header_guard |
build/include |
+ build/include_alpha |
build/include_order |
build/include_what_you_use |
build/namespaces |
@@ -149,7 +176,9 @@ |
runtime/int |
runtime/init |
runtime/invalid_increment |
+ runtime/member_string_references |
runtime/memset |
+ runtime/operator |
runtime/printf |
runtime/printf_format |
runtime/references |
@@ -179,7 +208,7 @@ |
# flag. By default all errors are on, so only add here categories that should be |
# off by default (i.e., categories that must be enabled by the --filter= flags). |
# All entries here should start with a '-' or '+', as in the --filter= flag. |
-_DEFAULT_FILTERS = [] |
+_DEFAULT_FILTERS = [ '-build/include_alpha' ] |
# 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 |
@@ -312,8 +341,41 @@ |
def __init__(self): |
dict.__init__(self) |
+ # The name of the current section. |
self._section = self._INITIAL_SECTION |
+ # The path of last found header. |
+ self._last_header = '' |
+ def CanonicalizeAlphabeticalOrder(self, header_path): |
+ """Returns a path canonicalized for alphabetical comparisson. |
+ |
+ - replaces "-" with "_" so they both cmp the same. |
+ - removes '-inl' since we don't require them to be after the main header. |
+ - lowercase everything, just in case. |
+ |
+ Args: |
+ header_path: Path to be canonicalized. |
+ |
+ Returns: |
+ Canonicalized path. |
+ """ |
+ return header_path.replace('-inl.h', '.h').replace('-', '_').lower() |
+ |
+ def IsInAlphabeticalOrder(self, header_path): |
+ """Check if a header is in alphabetical order with the previous header. |
+ |
+ Args: |
+ header_path: Header to be checked. |
+ |
+ Returns: |
+ Returns true if the header is in alphabetical order. |
+ """ |
+ canonical_header = self.CanonicalizeAlphabeticalOrder(header_path) |
+ if self._last_header > canonical_header: |
+ return False |
+ self._last_header = canonical_header |
+ return True |
+ |
def CheckNextIncludeOrder(self, header_type): |
"""Returns a non-empty error message if the next header is out of order. |
@@ -332,15 +394,19 @@ |
(self._TYPE_NAMES[header_type], |
self._SECTION_NAMES[self._section])) |
+ last_section = self._section |
+ |
if header_type == _C_SYS_HEADER: |
if self._section <= self._C_SECTION: |
self._section = self._C_SECTION |
else: |
+ self._last_header = '' |
return error_message |
elif header_type == _CPP_SYS_HEADER: |
if self._section <= self._CPP_SECTION: |
self._section = self._CPP_SECTION |
else: |
+ self._last_header = '' |
return error_message |
elif header_type == _LIKELY_MY_HEADER: |
if self._section <= self._MY_H_SECTION: |
@@ -358,6 +424,9 @@ |
assert header_type == _OTHER_HEADER |
self._section = self._OTHER_H_SECTION |
+ if last_section != self._section: |
+ self._last_header = '' |
+ |
return '' |
@@ -369,6 +438,8 @@ |
self.error_count = 0 # global count of reported errors |
# filters to apply when emitting error messages |
self.filters = _DEFAULT_FILTERS[:] |
+ self.counting = 'total' # In what way are we counting errors? |
+ self.errors_by_category = {} # string to int dict storing error counts |
# output format: |
# "emacs" - format that emacs can parse (default) |
@@ -385,6 +456,10 @@ |
self.verbose_level = level |
return last_verbose_level |
+ def SetCountingStyle(self, counting_style): |
+ """Sets the module's counting options.""" |
+ self.counting = counting_style |
+ |
def SetFilters(self, filters): |
"""Sets the error-message filters. |
@@ -410,14 +485,27 @@ |
raise ValueError('Every filter in --filters must start with + or -' |
' (%s does not)' % filt) |
- def ResetErrorCount(self): |
+ def ResetErrorCounts(self): |
"""Sets the module's error statistic back to zero.""" |
self.error_count = 0 |
+ self.errors_by_category = {} |
- def IncrementErrorCount(self): |
+ def IncrementErrorCount(self, category): |
"""Bumps the module's error statistic.""" |
self.error_count += 1 |
+ if self.counting in ('toplevel', 'detailed'): |
+ if self.counting != 'detailed': |
+ category = category.split('/')[0] |
+ if category not in self.errors_by_category: |
+ self.errors_by_category[category] = 0 |
+ self.errors_by_category[category] += 1 |
+ def PrintErrorCounts(self): |
+ """Print a summary of errors by category, and the total.""" |
+ for category, count in self.errors_by_category.iteritems(): |
+ sys.stderr.write('Category \'%s\' errors found: %d\n' % |
+ (category, count)) |
+ sys.stderr.write('Total errors found: %d\n' % self.error_count) |
_cpplint_state = _CppLintState() |
@@ -442,6 +530,11 @@ |
return _cpplint_state.SetVerboseLevel(level) |
+def _SetCountingStyle(level): |
+ """Sets the module's counting options.""" |
+ _cpplint_state.SetCountingStyle(level) |
+ |
+ |
def _Filters(): |
"""Returns the module's list of output filters, as a list.""" |
return _cpplint_state.filters |
@@ -650,7 +743,7 @@ |
# There are two ways we might decide not to print an error message: |
# the verbosity level isn't high enough, or the filters filter it out. |
if _ShouldPrintError(category, confidence): |
- _cpplint_state.IncrementErrorCount() |
+ _cpplint_state.IncrementErrorCount(category) |
if _cpplint_state.output_format == 'vs7': |
sys.stderr.write('%s(%s): %s [%s] [%d]\n' % ( |
filename, linenum, message, category, confidence)) |
@@ -913,7 +1006,7 @@ |
# The guard should be PATH_FILE_H_, but we also allow PATH_FILE_H__ |
# for backward compatibility. |
- if ifndef != cppvar: |
+ if ifndef != cppvar and not Search(r'\bNOLINT\b', lines[ifndef_linenum]): |
error_level = 0 |
if ifndef != cppvar + '_': |
error_level = 5 |
@@ -921,7 +1014,8 @@ |
error(filename, ifndef_linenum, 'build/header_guard', error_level, |
'#ifndef header guard has wrong style, please use: %s' % cppvar) |
- if endif != ('#endif // %s' % cppvar): |
+ if (endif != ('#endif // %s' % cppvar) and |
+ not Search(r'\bNOLINT\b', lines[endif_linenum])): |
error_level = 0 |
if endif != ('#endif // %s' % (cppvar + '_')): |
error_level = 5 |
@@ -1049,14 +1143,14 @@ |
'...) for improved thread safety.') |
-# Matches invalid increment: *count++, which moves pointer insead of |
+# Matches invalid increment: *count++, which moves pointer instead of |
# incrementing a value. |
-_RE_PATTERN_IVALID_INCREMENT = re.compile( |
+_RE_PATTERN_INVALID_INCREMENT = re.compile( |
r'^\s*\*\w+(\+\+|--);') |
def CheckInvalidIncrement(filename, clean_lines, linenum, error): |
- """Checks for invalud increment *count++. |
+ """Checks for invalid increment *count++. |
For example following function: |
void increment_counter(int* count) { |
@@ -1072,7 +1166,7 @@ |
error: The function to call with any errors found. |
""" |
line = clean_lines.elided[linenum] |
- if _RE_PATTERN_IVALID_INCREMENT.match(line): |
+ if _RE_PATTERN_INVALID_INCREMENT.match(line): |
error(filename, linenum, 'runtime/invalid_increment', 5, |
'Changing pointer instead of value (or unused value of operator*).') |
@@ -1136,8 +1230,9 @@ |
- classes with virtual methods need virtual destructors (compiler warning |
available, but not turned on yet.) |
- Additionally, check for constructor/destructor style violations as it |
- is very convenient to do so while checking for gcc-2 compliance. |
+ Additionally, check for constructor/destructor style violations and reference |
+ members, as it is very convenient to do so while checking for |
+ gcc-2 compliance. |
Args: |
filename: The name of the current file. |
@@ -1191,6 +1286,18 @@ |
error(filename, linenum, 'build/deprecated', 3, |
'>? and <? (max and min) operators are non-standard and deprecated.') |
+ if Search(r'^\s*const\s*string\s*&\s*\w+\s*;', line): |
+ # TODO(unknown): Could it be expanded safely to arbitrary references, |
+ # without triggering too many false positives? The first |
+ # attempt triggered 5 warnings for mostly benign code in the regtest, hence |
+ # the restriction. |
+ # Here's the original regexp, for the reference: |
+ # type_name = r'\w+((\s*::\s*\w+)|(\s*<\s*\w+?\s*>))?' |
+ # r'\s*const\s*' + type_name + '\s*&\s*\w+\s*;' |
+ error(filename, linenum, 'runtime/member_string_references', 2, |
+ 'const string& members are dangerous. It is much better to use ' |
+ 'alternatives, such as pointers or simple constants.') |
+ |
# Track class entry and exit, and attempt to find cases within the |
# class declaration that don't meet the C++ style |
# guidelines. Tracking is very dependent on the code matching Google |
@@ -2131,6 +2238,9 @@ |
error(filename, linenum, 'build/include_order', 4, |
'%s. Should be: %s.h, c system, c++ system, other.' % |
(error_message, fileinfo.BaseName())) |
+ if not include_state.IsInAlphabeticalOrder(include): |
+ error(filename, linenum, 'build/include_alpha', 4, |
+ 'Include "%s" not in alphabetical order' % include) |
# Look for any of the stream classes that are part of standard C++. |
match = _RE_PATTERN_INCLUDE.match(line) |
@@ -2209,16 +2319,18 @@ |
# Parameterless conversion functions, such as bool(), are allowed as they are |
# probably a member operator declaration or default constructor. |
match = Search( |
- r'\b(int|float|double|bool|char|int32|uint32|int64|uint64)\([^)]', line) |
+ r'(\bnew\s+)?\b' # Grab 'new' operator, if it's there |
+ r'(int|float|double|bool|char|int32|uint32|int64|uint64)\([^)]', line) |
if match: |
# gMock methods are defined using some variant of MOCK_METHODx(name, type) |
# where type may be float(), int(string), etc. Without context they are |
# virtually indistinguishable from int(x) casts. |
- if not Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line): |
+ 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)): |
error(filename, linenum, 'readability/casting', 4, |
'Using deprecated casting style. ' |
'Use static_cast<%s>(...) instead' % |
- match.group(1)) |
+ match.group(2)) |
CheckCStyleCast(filename, linenum, line, clean_lines.raw_lines[linenum], |
'static_cast', |
@@ -2305,6 +2417,16 @@ |
error(filename, linenum, 'runtime/printf', 1, |
'sscanf can be ok, but is slow and can overflow buffers.') |
+ # Check if some verboten operator overloading is going on |
+ # TODO(unknown): catch out-of-line unary operator&: |
+ # class X {}; |
+ # int operator&(const X& x) { return 42; } // unary operator& |
+ # The trick is it's hard to tell apart from binary operator&: |
+ # class Y { int operator&(const Y& x) { return 23; } }; // binary operator& |
+ if Search(r'\boperator\s*&\s*\(\s*\)', line): |
+ error(filename, linenum, 'runtime/operator', 4, |
+ 'Unary operator& is dangerous. Do not use it.') |
+ |
# Check for suspicious usage of "if" like |
# } if (a == b) { |
if Search(r'\}\s*if\s*\(', line): |
@@ -2861,6 +2983,7 @@ |
""" |
try: |
(opts, filenames) = getopt.getopt(args, '', ['help', 'output=', 'verbose=', |
+ 'counting=', |
'filter=']) |
except getopt.GetoptError: |
PrintUsage('Invalid arguments.') |
@@ -2868,6 +2991,7 @@ |
verbosity = _VerboseLevel() |
output_format = _OutputFormat() |
filters = '' |
+ counting_style = '' |
for (opt, val) in opts: |
if opt == '--help': |
@@ -2882,6 +3006,10 @@ |
filters = val |
if not filters: |
PrintCategories() |
+ elif opt == '--counting': |
+ if val not in ('total', 'toplevel', 'detailed'): |
+ PrintUsage('Valid counting options are total, toplevel, and detailed') |
+ counting_style = val |
if not filenames: |
PrintUsage('No files were specified.') |
@@ -2889,6 +3017,7 @@ |
_SetOutputFormat(output_format) |
_SetVerboseLevel(verbosity) |
_SetFilters(filters) |
+ _SetCountingStyle(counting_style) |
return filenames |
@@ -2903,10 +3032,11 @@ |
codecs.getwriter('utf8'), |
'replace') |
- _cpplint_state.ResetErrorCount() |
+ _cpplint_state.ResetErrorCounts() |
for filename in filenames: |
ProcessFile(filename, _cpplint_state.verbose_level) |
- sys.stderr.write('Total errors found: %d\n' % _cpplint_state.error_count) |
+ _cpplint_state.PrintErrorCounts() |
+ |
sys.exit(_cpplint_state.error_count > 0) |