diff --git a/clint.py b/clint.py index 097b54f1c1..0eaab6e3f6 100755 --- a/clint.py +++ b/clint.py @@ -56,12 +56,15 @@ import sre_compile import string import sys import unicodedata +import json +import collections # for defaultdict _USAGE = """ Syntax: clint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...] [--counting=total|toplevel|detailed] [--root=subdir] - [--linelength=digits] + [--linelength=digits] [--record-errors=file] + [--suppress-errors=file] [file] ... The style guidelines this tries to follow are those in @@ -156,6 +159,13 @@ Syntax: clint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...] Examples: --extensions=hpp,cpp + + record-errors=file + Record errors to the given location. This file may later be used for error + suppression using suppress-errors flag. + + suppress-errors=file + Errors listed in the given file will not be reported. """ # We categorize each error message we print. Here are the categories. @@ -270,6 +280,10 @@ _RE_SUPPRESSION = re.compile(r'\bNOLINT\b(\([^)]*\))?') # on which those errors are expected and should be suppressed. _error_suppressions = {} +# {(str, int)}: a set of error categories and line numbers which are expected to +# be suppressed +_error_suppressions_2 = set() + # The allowed line length of files. # This is set by --linelength flag. _line_length = 80 @@ -309,11 +323,30 @@ def ParseNolintSuppressions(filename, raw_line, linenum, error): 'Unknown NOLINT error category: %s' % category) +def ParseKnownErrorSuppressions(filename, raw_lines, linenum): + """Updates the global list of error-suppressions from suppress-file. + + Args: + filename: str, the name of the input file. + raw_lines: list, all file lines + linenum: int, the number of the current line. + """ + key = tuple(raw_lines[linenum - 1 if linenum else 0:linenum + 2]) + if key in _cpplint_state.suppressed_errors[filename]: + for category in _cpplint_state.suppressed_errors[filename][key]: + _error_suppressions_2.add((category, linenum)) + + def ResetNolintSuppressions(): "Resets the set of NOLINT suppressions to empty." _error_suppressions.clear() +def ResetKnownErrorSuppressions(): + "Resets the set of suppress-errors=file suppressions to empty." + _error_suppressions_2.clear() + + def IsErrorSuppressedByNolint(category, linenum): """Returns true if the specified error category is suppressed on this line. @@ -330,6 +363,19 @@ def IsErrorSuppressedByNolint(category, linenum): linenum in _error_suppressions.get(None, set())) +def IsErrorInSuppressedErrorsList(category, linenum): + """Returns true if the specified error is suppressed by suppress-errors=file + + 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 presense in + suppressions file. + """ + return (category, linenum) in _error_suppressions_2 + + def Match(pattern, s): """Matches the string with the pattern, caching the compiled regexp.""" # The regexp compilation caching is inlined in both Match and Search for @@ -454,6 +500,10 @@ class _CppLintState(object): # "vs7" - format that Microsoft Visual Studio 7 can parse self.output_format = 'emacs' + self.record_errors_file = None + self.suppressed_errors = collections.defaultdict( + lambda: collections.defaultdict(set)) + def SetOutputFormat(self, output_format): """Sets the output format for errors.""" self.output_format = output_format @@ -517,6 +567,25 @@ class _CppLintState(object): (category, count)) sys.stderr.write('Total errors found: %d\n' % self.error_count) + def SuppressErrorsFrom(self, fname): + """Open file and read a list of suppressed errors from it""" + if fname is None: + return + try: + with open(fname) as fp: + for line in fp: + fname, lines, category = json.loads(line) + lines = tuple(lines) + self.suppressed_errors[fname][lines].add(category) + except IOError: + pass + + def RecordErrorsTo(self, fname): + """Open file with suppressed errors for writing""" + if fname is None: + return + self.record_errors_file = open(fname, 'w') + _cpplint_state = _CppLintState() @@ -545,6 +614,16 @@ def _SetCountingStyle(level): _cpplint_state.SetCountingStyle(level) +def _SuppressErrorsFrom(fname): + """Sets the file containing suppressed errors.""" + _cpplint_state.SuppressErrorsFrom(fname) + + +def _RecordErrorsTo(fname): + """Sets the file containing suppressed errors to write to.""" + _cpplint_state.RecordErrorsTo(fname) + + def _Filters(): """Returns the module's list of output filters, as a list.""" return _cpplint_state.filters @@ -687,6 +766,8 @@ def _ShouldPrintError(category, confidence, linenum): # the verbosity level isn't high enough, or the filters filter it out. if IsErrorSuppressedByNolint(category, linenum): return False + if IsErrorInSuppressedErrorsList(category, linenum): + return False if confidence < _cpplint_state.verbose_level: return False @@ -2986,6 +3067,23 @@ def ProcessFileData(filename, file_extension, lines, error, nesting_state = _NestingState() ResetNolintSuppressions() + ResetKnownErrorSuppressions() + + for line in range(1, len(lines)): + ParseKnownErrorSuppressions(filename, lines, line) + + if _cpplint_state.record_errors_file: + raw_lines = lines[:] + + def RecordedError(filename, linenum, category, confidence, message): + if not IsErrorSuppressedByNolint(category, linenum): + key = raw_lines[linenum - 1 if linenum else 0:linenum + 2] + err = [filename, key, category] + json.dump(err, _cpplint_state.record_errors_file) + _cpplint_state.record_errors_file.write('\n') + Error(filename, linenum, category, confidence, message) + + error = RecordedError if file_extension == 'h': CheckForHeaderGuard(filename, lines, error) @@ -3113,7 +3211,9 @@ def ParseArguments(args): 'filter=', 'root=', 'linelength=', - 'extensions=']) + 'extensions=', + 'record-errors=', + 'suppress-errors=']) except getopt.GetoptError: PrintUsage('Invalid arguments.') @@ -3121,6 +3221,8 @@ def ParseArguments(args): output_format = _OutputFormat() filters = '' counting_style = '' + record_errors_file = None + suppress_errors_file = None for (opt, val) in opts: if opt == '--help': @@ -3153,6 +3255,10 @@ def ParseArguments(args): _valid_extensions = set(val.split(',')) except ValueError: PrintUsage('Extensions must be comma separated list.') + elif opt == '--record-errors': + record_errors_file = val + elif opt == '--suppress-errors': + suppress_errors_file = val if not filenames: PrintUsage('No files were specified.') @@ -3161,6 +3267,8 @@ def ParseArguments(args): _SetVerboseLevel(verbosity) _SetFilters(filters) _SetCountingStyle(counting_style) + _SuppressErrorsFrom(suppress_errors_file) + _RecordErrorsTo(record_errors_file) return filenames