diff --git a/cpplint.py b/cpplint.py index 432f18c369..d51ad78ef7 100755 --- a/cpplint.py +++ b/cpplint.py @@ -70,6 +70,9 @@ Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...] 'NOLINT(category)' comment to the line. NOLINT or NOLINT(*) suppresses errors of all categories on that line. + To suppress false-positive errors for a block of lines, enclose the + block with 'NOLINTBEGIN(category)' and 'NOLINTEND' comment lines. + The files passed in will be linted; at least one file must be provided. Default linted extensions are .cc, .cpp, .cu, .cuh and .h. Change the extensions with the --extensions flag. @@ -785,7 +788,29 @@ _valid_extensions = set(['cc', 'h', 'cpp', 'cu', 'cuh']) _global_error_suppressions = {} -def ParseNolintSuppressions(filename, raw_line, linenum, error): +def AddSuppression(suppressed_line, category): + """Update global error_suppressions for a single line. + + Args: + suppressed_line: int, line number where suppression is to be added. + category: str, category to suppress, or None to suppress all. + """ + if category: + if category.startswith('(') and category.endswith(')'): + category = category[1:-1] + if category == '*': + category = None # "(*)" -> Suppress all + + if category is None or category in _ERROR_CATEGORIES: + _error_suppressions.setdefault(category, set()).add(suppressed_line) + else: + # Unknown category. We used to track these in an _LEGACY_ERROR_CATEGORIES + # list, but these days we just silently ignore them since they may be + # intended for ClangTidy. + pass + + +def ParseNolintSuppressions(filename, raw_lines, linenum, error): """Updates the global list of line error-suppressions. Parses any NOLINT comments on the current line, updating the global @@ -794,28 +819,54 @@ def ParseNolintSuppressions(filename, raw_line, linenum, error): Args: filename: str, the name of the input file. - raw_line: str, the line of input text, with comments. + raw_lines: List[str], list of input lines, with comments. linenum: int, the number of the current line. error: function, an error handler. """ - matched = Search(r'\bNOLINT(NEXTLINE)?\b(\([^)]+\))?', raw_line) + matched = Search(r'\bNOLINT(NEXTLINE|BEGIN)?\b(\([^)]+\))?', + raw_lines[linenum]) if matched: - if matched.group(1): - suppressed_line = linenum + 1 - else: - suppressed_line = linenum + annotation_type = matched.group(1) category = matched.group(2) - if category in (None, '(*)'): # => "suppress all" - _error_suppressions.setdefault(None, set()).add(suppressed_line) - else: - if category.startswith('(') and category.endswith(')'): - category = category[1:-1] - if category in _ERROR_CATEGORIES: - _error_suppressions.setdefault(category, - set()).add(suppressed_line) - elif category not in _LEGACY_ERROR_CATEGORIES: + if annotation_type: + # Suppressed line(s) are not the current line. + if annotation_type == 'NEXTLINE': + # Silence next line only. + AddSuppression(linenum + 1, category) + else: + # Silence a block of lines. + # + # Note that multiple NOLINTBEGIN lines may be terminated by a + # single NOLINTEND line: + # + # // NOLINTBEGIN(some-category) + # // NOLINTBEGIN(different-category) + # ... + # // NOLINTEND <- ends both silences + # + # Also note that because we only consume one single NOLINT + # annotation per line, in order to silence multiple categories + # of warnings, the above example would be the preferred way to + # do it. This is actually an improvement over historical + # handling of single line NOLINT annotations, where the choices + # were to silence 0 or 1 or all categories on a single line. + lastline = None + for i in range(linenum + 1, len(raw_lines)): + if Search(r'\bNOLINTEND\b', raw_lines[i]): + lastline = i + break + if not lastline: + # Because multiple NOLINTBEGIN may be terminated by a single + # NOLINTEND, the error message here deliberately avoids + # something like "Unmatched NOLINTEND". error(filename, linenum, 'readability/nolint', 5, - 'Unknown NOLINT error category: %s' % category) + 'Missing NOLINTEND') + else: + for i in range(linenum + 1, lastline): + AddSuppression(i, category) + else: + # Suppressing errors on current line. + AddSuppression(linenum, category) def ProcessGlobalSuppresions(lines): @@ -2158,14 +2209,12 @@ def CheckForHeaderGuard(filename, clean_lines, error): if ifndef != cppvar + '_': error_level = 5 - ParseNolintSuppressions(filename, raw_lines[ifndef_linenum], - ifndef_linenum, error) + ParseNolintSuppressions(filename, raw_lines, ifndef_linenum, error) error(filename, ifndef_linenum, 'build/header_guard', error_level, '#ifndef header guard has wrong style, please use: %s' % cppvar) # Check for "//" comments on endif line. - ParseNolintSuppressions(filename, raw_lines[endif_linenum], endif_linenum, - error) + ParseNolintSuppressions(filename, raw_lines, endif_linenum, error) match = Match(r'#endif\s*//\s*' + cppvar + r'(_)?\b', endif) if match: if match.group(1) == '_': @@ -6137,7 +6186,7 @@ def ProcessLine(filename, clean_lines, line, error """ raw_lines = clean_lines.raw_lines - ParseNolintSuppressions(filename, raw_lines[line], line, error) + ParseNolintSuppressions(filename, raw_lines, line, error) nesting_state.Update(filename, clean_lines, line, error) CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, error)