cpplint.py 179 KB
Newer Older
James Zern's avatar
James Zern committed
            Match(r'^\s*$', indent)):
          if classinfo.is_struct:
            parent = 'struct ' + classinfo.name
          else:
            parent = 'class ' + classinfo.name
          slots = ''
          if access_match.group(3):
            slots = access_match.group(3)
          error(filename, linenum, 'whitespace/indent', 3,
                '%s%s: should be indented +1 space inside %s' % (
                    access_match.group(2), slots, parent))
James Zern's avatar
James Zern committed

    # Consume braces or semicolons from what's left of the line
    while True:
      # Match first brace, semicolon, or closed parenthesis.
      matched = Match(r'^[^{;)}]*([{;)}])(.*)$', line)
      if not matched:
        break

      token = matched.group(1)
      if token == '{':
        # If namespace or class hasn't seen a opening brace yet, mark
        # namespace/class head as complete.  Push a new block onto the
        # stack otherwise.
        if not self.SeenOpenBrace():
          self.stack[-1].seen_open_brace = True
        else:
          self.stack.append(_BlockInfo(True))
          if _MATCH_ASM.match(line):
            self.stack[-1].inline_asm = _BLOCK_ASM
      elif token == ';' or token == ')':
        # If we haven't seen an opening brace yet, but we already saw
        # a semicolon, this is probably a forward declaration.  Pop
        # the stack for these.
        #
        # Similarly, if we haven't seen an opening brace yet, but we
        # already saw a closing parenthesis, then these are probably
        # function arguments with extra "class" or "struct" keywords.
        # Also pop these stack for these.
        if not self.SeenOpenBrace():
          self.stack.pop()
      else:  # token == '}'
        # Perform end of block checks and pop the stack.
        if self.stack:
          self.stack[-1].CheckEnd(filename, clean_lines, linenum, error)
          self.stack.pop()
      line = matched.group(2)

  def InnermostClass(self):
    """Get class info on the top of the stack.

    Returns:
      A _ClassInfo object if we are inside a class, or None otherwise.
    """
    for i in range(len(self.stack), 0, -1):
      classinfo = self.stack[i - 1]
      if isinstance(classinfo, _ClassInfo):
        return classinfo
    return None

James Zern's avatar
James Zern committed
  def CheckCompletedBlocks(self, filename, error):
    """Checks that all classes and namespaces have been completely parsed.

    Call this when all lines in a file have been processed.
    Args:
      filename: The name of the current file.
      error: The function to call with any errors found.
    """
James Zern's avatar
James Zern committed
    # Note: This test can result in false positives if #ifdef constructs
    # get in the way of brace matching. See the testBuildClass test in
    # cpplint_unittest.py for an example of this.
    for obj in self.stack:
      if isinstance(obj, _ClassInfo):
        error(filename, obj.starting_linenum, 'build/class', 5,
              'Failed to find complete declaration of class %s' %
              obj.name)
James Zern's avatar
James Zern committed
      elif isinstance(obj, _NamespaceInfo):
        error(filename, obj.starting_linenum, 'build/namespaces', 5,
              'Failed to find complete declaration of namespace %s' %
              obj.name)


def CheckForNonStandardConstructs(filename, clean_lines, linenum,
James Zern's avatar
James Zern committed
                                  nesting_state, error):
James Zern's avatar
James Zern committed
  r"""Logs an error if we see certain non-ANSI constructs ignored by gcc-2.

  Complain about several constructs which gcc-2 accepts, but which are
  not standard C++.  Warning about these in lint is one way to ease the
  transition to new compilers.
  - put storage class first (e.g. "static const" instead of "const static").
  - "%lld" instead of %qd" in printf-type functions.
  - "%1$d" is non-standard in printf-type functions.
  - "\%" is an undefined character escape sequence.
  - text after #endif is not allowed.
  - invalid inner-style forward declaration.
  - >? and <? operators, and their >?= and <?= cousins.

  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.
    clean_lines: A CleansedLines instance containing the file.
    linenum: The number of the line to check.
James Zern's avatar
James Zern committed
    nesting_state: A _NestingState instance which maintains information about
                   the current stack of nested blocks being parsed.
    error: A callable to which errors are reported, which takes 4 arguments:
           filename, line number, error level, and message
  """

  # Remove comments from the line, but leave in strings for now.
  line = clean_lines.lines[linenum]

  if Search(r'printf\s*\(.*".*%[-+ ]?\d*q', line):
    error(filename, linenum, 'runtime/printf_format', 3,
          '%q in format strings is deprecated.  Use %ll instead.')

  if Search(r'printf\s*\(.*".*%\d+\$', line):
    error(filename, linenum, 'runtime/printf_format', 2,
          '%N$ formats are unconventional.  Try rewriting to avoid them.')

  # Remove escaped backslashes before looking for undefined escapes.
  line = line.replace('\\\\', '')

  if Search(r'("|\').*\\(%|\[|\(|{)', line):
    error(filename, linenum, 'build/printf_format', 3,
          '%, [, (, and { are undefined character escapes.  Unescape them.')

  # For the rest, work with both comments and strings removed.
  line = clean_lines.elided[linenum]

  if Search(r'\b(const|volatile|void|char|short|int|long'
            r'|float|double|signed|unsigned'
            r'|schar|u?int8|u?int16|u?int32|u?int64)'
James Zern's avatar
James Zern committed
            r'\s+(register|static|extern|typedef)\b',
            line):
    error(filename, linenum, 'build/storage_class', 5,
          'Storage class (static, extern, typedef, etc) should be first.')

  if Match(r'\s*#\s*endif\s*[^/\s]+', line):
    error(filename, linenum, 'build/endif_comment', 5,
          'Uncommented text after #endif is non-standard.  Use a comment.')

  if Match(r'\s*class\s+(\w+\s*::\s*)+\w+\s*;', line):
    error(filename, linenum, 'build/forward_decl', 5,
          'Inner-style forward declarations are invalid.  Remove this line.')

  if Search(r'(\w+|[+-]?\d+(\.\d*)?)\s*(<|>)\?=?\s*(\w+|[+-]?\d+)(\.\d*)?',
            line):
    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.')

James Zern's avatar
James Zern committed
  # Everything else in this function operates on class declarations.
  # Return early if the top of the nesting stack is not a class, or if
  # the class head is not completed yet.
  classinfo = nesting_state.InnermostClass()
  if not classinfo or not classinfo.seen_open_brace:
    return

  # The class may have been declared with namespace or classname qualifiers.
  # The constructor and destructor will not have those qualifiers.
  base_classname = classinfo.name.split('::')[-1]

  # Look for single-argument constructors that aren't marked explicit.
  # Technically a valid construct, but against style.
  args = Match(r'\s+(?:inline\s+)?%s\s*\(([^,()]+)\)'
               % re.escape(base_classname),
               line)
  if (args and
      args.group(1) != 'void' and
James Zern's avatar
James Zern committed
      not Match(r'(const\s+)?%s(\s+const)?\s*(?:<\w+>\s*)?&'
                % re.escape(base_classname), args.group(1).strip())):
    error(filename, linenum, 'runtime/explicit', 5,
          'Single-argument constructors should be marked explicit.')


def CheckSpacingForFunctionCall(filename, line, linenum, error):
  """Checks for the correctness of various spacing around function calls.

  Args:
    filename: The name of the current file.
    line: The text of the line to check.
    linenum: The number of the line to check.
    error: The function to call with any errors found.
  """

  # Since function calls often occur inside if/for/while/switch
  # expressions - which have their own, more liberal conventions - we
  # first see if we should be looking inside such an expression for a
  # function call, to which we can apply more strict standards.
  fncall = line    # if there's no control flow construct, look at whole line
  for pattern in (r'\bif\s*\((.*)\)\s*{',
                  r'\bfor\s*\((.*)\)\s*{',
                  r'\bwhile\s*\((.*)\)\s*[{;]',
                  r'\bswitch\s*\((.*)\)\s*{'):
    match = Search(pattern, line)
    if match:
      fncall = match.group(1)    # look inside the parens for function calls
      break

  # Except in if/for/while/switch, there should never be space
  # immediately inside parens (eg "f( 3, 4 )").  We make an exception
  # for nested parens ( (a+b) + c ).  Likewise, there should never be
  # a space before a ( when it's a function argument.  I assume it's a
  # function argument when the char before the whitespace is legal in
  # a function name (alnum + _) and we're not starting a macro. Also ignore
  # pointers and references to arrays and functions coz they're too tricky:
  # we use a very simple way to recognize these:
  # " (something)(maybe-something)" or
  # " (something)(maybe-something," or
  # " (something)[something]"
  # Note that we assume the contents of [] to be short enough that
  # they'll never need to wrap.
  if (  # Ignore control structures.
James Zern's avatar
James Zern committed
      not Search(r'\b(if|for|while|switch|return|new|delete|catch|sizeof)\b',
                 fncall) and
      # Ignore pointers/references to functions.
      not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and
      # Ignore pointers/references to arrays.
      not Search(r' \([^)]+\)\[[^\]]+\]', fncall)):
    if Search(r'\w\s*\(\s(?!\s*\\$)', fncall):      # a ( used for a fn call
      error(filename, linenum, 'whitespace/parens', 4,
            'Extra space after ( in function call')
    elif Search(r'\(\s+(?!(\s*\\)|\()', fncall):
      error(filename, linenum, 'whitespace/parens', 2,
            'Extra space after (')
    if (Search(r'\w\s+\(', fncall) and
James Zern's avatar
James Zern committed
        not Search(r'#\s*define|typedef', fncall) and
James Zern's avatar
James Zern committed
        not Search(r'\w\s+\((\w+::)*\*\w+\)\(', fncall)):
      error(filename, linenum, 'whitespace/parens', 4,
            'Extra space before ( in function call')
    # 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
    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,
              'Extra space before )')


def IsBlankLine(line):
  """Returns true if the given line is blank.

  We consider a line to be blank if the line is empty or consists of
  only white spaces.

  Args:
    line: A line of a string.

  Returns:
    True, if the given line is blank.
  """
  return not line or line.isspace()


def CheckForFunctionLengths(filename, clean_lines, linenum,
                            function_state, error):
  """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

  Uses a simplistic algorithm assuming other style guidelines
  (especially spacing) are followed.
  Only checks unindented functions, so class members are unchecked.
  Trivial bodies are unchecked, so constructors with huge initializer lists
  may be missed.
  Blank/comment lines are not counted so as to avoid encouraging the removal
  of vertical space and comments just to get through a lint check.
  NOLINT *on the last line of a function* disables this check.

  Args:
    filename: The name of the current file.
    clean_lines: A CleansedLines instance containing the file.
    linenum: The number of the line to check.
    function_state: Current function name and lines in body so far.
    error: The function to call with any errors found.
  """
  lines = clean_lines.lines
  line = lines[linenum]
  raw = clean_lines.raw_lines
  raw_line = raw[linenum]
  joined_line = ''

  starting_func = False
  regexp = r'(\w(\w|::|\*|\&|\s)*)\('  # decls * & space::name( ...
  match_result = Match(regexp, line)
  if match_result:
    # If the name is all caps and underscores, figure it's a macro and
    # ignore it, unless it's TEST or TEST_F.
    function_name = match_result.group(1).split()[-1]
    if function_name == 'TEST' or function_name == 'TEST_F' or (
        not Match(r'[A-Z_]+$', function_name)):
      starting_func = True

  if starting_func:
    body_found = False
    for start_linenum in xrange(linenum, clean_lines.NumLines()):
      start_line = lines[start_linenum]
      joined_line += ' ' + start_line.lstrip()
      if Search(r'(;|})', start_line):  # Declarations and trivial functions
        body_found = True
        break                              # ... ignore
      elif Search(r'{', start_line):
        body_found = True
        function = Search(r'((\w|:)*)\(', line).group(1)
        if Match(r'TEST', function):    # Handle TEST... macros
          parameter_regexp = Search(r'(\(.*\))', joined_line)
          if parameter_regexp:             # Ignore bad syntax
            function += parameter_regexp.group(1)
        else:
          function += '()'
        function_state.Begin(function)
        break
    if not body_found:
      # No body for the function (or evidence of a non-function) was found.
      error(filename, linenum, 'readability/fn_size', 5,
            'Lint failed to find start of function body.')
  elif Match(r'^\}\s*$', line):  # function end
    function_state.Check(error, filename, linenum)
    function_state.End()
  elif not Match(r'^\s*$', line):
    function_state.Count()  # Count non-blank/non-comment lines.


_RE_PATTERN_TODO = re.compile(r'^//(\s*)TODO(\(.+?\))?:?(\s|$)?')


def CheckComment(comment, filename, linenum, error):
  """Checks for common mistakes in TODO comments.

  Args:
    comment: The text of the comment from the line in question.
    filename: The name of the current file.
    linenum: The number of the line to check.
    error: The function to call with any errors found.
  """
  match = _RE_PATTERN_TODO.match(comment)
  if match:
    # One whitespace is correct; zero whitespace is handled elsewhere.
    leading_whitespace = match.group(1)
    if len(leading_whitespace) > 1:
      error(filename, linenum, 'whitespace/todo', 2,
            'Too many spaces before TODO')

    username = match.group(2)
    if not username:
      error(filename, linenum, 'readability/todo', 2,
            'Missing username in TODO; it should look like '
            '"// TODO(my_username): Stuff."')

    middle_whitespace = match.group(3)
James Zern's avatar
James Zern committed
    # Comparisons made explicit for correctness -- pylint: disable=g-explicit-bool-comparison
    if middle_whitespace != ' ' and middle_whitespace != '':
      error(filename, linenum, 'whitespace/todo', 2,
            'TODO(my_username) should be followed by a space')

James Zern's avatar
James Zern committed
def CheckAccess(filename, clean_lines, linenum, nesting_state, error):
  """Checks for improper use of DISALLOW* macros.

  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]  # get rid of comments and strings

  matched = Match((r'\s*(DISALLOW_COPY_AND_ASSIGN|'
                   r'DISALLOW_EVIL_CONSTRUCTORS|'
                   r'DISALLOW_IMPLICIT_CONSTRUCTORS)'), line)
  if not matched:
    return
  if nesting_state.stack and isinstance(nesting_state.stack[-1], _ClassInfo):
    if nesting_state.stack[-1].access != 'private':
      error(filename, linenum, 'readability/constructors', 3,
            '%s must be in the private: section' % matched.group(1))

  else:
    # Found DISALLOW* macro outside a class declaration, or perhaps it
    # was used inside a function when it should have been part of the
    # class declaration.  We could issue a warning here, but it
    # probably resulted in a compiler error already.
    pass


def FindNextMatchingAngleBracket(clean_lines, linenum, init_suffix):
  """Find the corresponding > to close a template.

  Args:
    clean_lines: A CleansedLines instance containing the file.
    linenum: Current line number.
    init_suffix: Remainder of the current line after the initial <.

  Returns:
    True if a matching bracket exists.
  """
  line = init_suffix
  nesting_stack = ['<']
  while True:
    # Find the next operator that can tell us whether < is used as an
    # opening bracket or as a less-than operator.  We only want to
    # warn on the latter case.
    #
    # We could also check all other operators and terminate the search
    # early, e.g. if we got something like this "a<b+c", the "<" is
    # most likely a less-than operator, but then we will get false
James Zern's avatar
James Zern committed
    # positives for default arguments and other template expressions.
James Zern's avatar
James Zern committed
    match = Search(r'^[^<>(),;\[\]]*([<>(),;\[\]])(.*)$', line)
    if match:
      # Found an operator, update nesting stack
      operator = match.group(1)
      line = match.group(2)

      if nesting_stack[-1] == '<':
        # Expecting closing angle bracket
        if operator in ('<', '(', '['):
          nesting_stack.append(operator)
        elif operator == '>':
          nesting_stack.pop()
          if not nesting_stack:
            # Found matching angle bracket
            return True
        elif operator == ',':
          # Got a comma after a bracket, this is most likely a template
          # argument.  We have not seen a closing angle bracket yet, but
          # it's probably a few lines later if we look for it, so just
          # return early here.
          return True
        else:
          # Got some other operator.
          return False

      else:
        # Expecting closing parenthesis or closing bracket
        if operator in ('<', '(', '['):
          nesting_stack.append(operator)
        elif operator in (')', ']'):
          # We don't bother checking for matching () or [].  If we got
          # something like (] or [), it would have been a syntax error.
          nesting_stack.pop()

    else:
      # Scan the next line
      linenum += 1
      if linenum >= len(clean_lines.elided):
        break
      line = clean_lines.elided[linenum]

  # Exhausted all remaining lines and still no matching angle bracket.
  # Most likely the input was incomplete, otherwise we should have
  # seen a semicolon and returned early.
  return True


def FindPreviousMatchingAngleBracket(clean_lines, linenum, init_prefix):
  """Find the corresponding < that started a template.

  Args:
    clean_lines: A CleansedLines instance containing the file.
    linenum: Current line number.
    init_prefix: Part of the current line before the initial >.

  Returns:
    True if a matching bracket exists.
  """
  line = init_prefix
  nesting_stack = ['>']
  while True:
    # Find the previous operator
    match = Search(r'^(.*)([<>(),;\[\]])[^<>(),;\[\]]*$', line)
    if match:
      # Found an operator, update nesting stack
      operator = match.group(2)
      line = match.group(1)

      if nesting_stack[-1] == '>':
        # Expecting opening angle bracket
        if operator in ('>', ')', ']'):
          nesting_stack.append(operator)
        elif operator == '<':
          nesting_stack.pop()
          if not nesting_stack:
            # Found matching angle bracket
            return True
        elif operator == ',':
          # Got a comma before a bracket, this is most likely a
          # template argument.  The opening angle bracket is probably
          # there if we look for it, so just return early here.
          return True
        else:
          # Got some other operator.
          return False

      else:
        # Expecting opening parenthesis or opening bracket
        if operator in ('>', ')', ']'):
          nesting_stack.append(operator)
        elif operator in ('(', '['):
          nesting_stack.pop()
James Zern's avatar
James Zern committed
    else:
      # Scan the previous line
      linenum -= 1
      if linenum < 0:
        break
      line = clean_lines.elided[linenum]

  # Exhausted all earlier lines and still no matching angle bracket.
  return False


def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
  """Checks for the correctness of various spacing issues in the code.

  Things we check for: spaces around operators, spaces after
  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
  line, don't end a function with a blank line, don't add a blank line
  after public/protected/private, don't have too many blank lines in a row.

  Args:
    filename: The name of the current file.
    clean_lines: A CleansedLines instance containing the file.
    linenum: The number of the line to check.
James Zern's avatar
James Zern committed
    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.
  """

James Zern's avatar
James Zern committed
  # Don't use "elided" lines here, otherwise we can't check commented lines.
  # Don't want to use "raw" either, because we don't want to check inside C++11
  # raw strings,
  raw = clean_lines.lines_without_raw_strings
  line = raw[linenum]

  # Before nixing comments, check if the line is blank for no good
  # reason.  This includes the first line after a block is opened, and
  # blank lines at the end of a function (ie, right before a line like '}'
James Zern's avatar
James Zern committed
  #
  # Skip all the blank line checks if we are immediately inside a
  # namespace body.  In other words, don't issue blank line warnings
  # for this block:
  #   namespace {
  #
  #   }
  #
  # A warning about missing end of namespace comments will be issued instead.
  if IsBlankLine(line) and not nesting_state.InNamespaceBody():
    elided = clean_lines.elided
    prev_line = elided[linenum - 1]
    prevbrace = prev_line.rfind('{')
    # TODO(unknown): Don't complain if line before blank line, and line after,
    #                both start with alnums and are indented the same amount.
    #                This ignores whitespace at the start of a namespace block
    #                because those are not usually indented.
James Zern's avatar
James Zern committed
    if prevbrace != -1 and prev_line[prevbrace:].find('}') == -1:
      # 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
      # 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
      # 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
      # initializers of a constructor do not fit into a 80 column line.
      exception = False
      if Match(r' {6}\w', prev_line):  # Initializer list?
        # We are looking for the opening column of initializer list, which
        # should be indented 4 spaces to cause 6 space indentation afterwards.
        search_position = linenum-2
        while (search_position >= 0
               and Match(r' {6}\w', elided[search_position])):
          search_position -= 1
        exception = (search_position >= 0
                     and elided[search_position][:5] == '    :')
      else:
        # Search for the function arguments or an initializer list.  We use a
        # simple heuristic here: If the line is indented 4 spaces; and we have a
        # closing paren, without the opening paren, followed by an opening brace
        # or colon (for initializer lists) we assume that it is the last line of
        # a function header.  If we have a colon indented 4 spaces, it is an
        # initializer list.
        exception = (Match(r' {4}\w[^\(]*\)\s*(const\s*)?(\{\s*$|:)',
                           prev_line)
                     or Match(r' {4}:', prev_line))

      if not exception:
        error(filename, linenum, 'whitespace/blank_line', 2,
James Zern's avatar
James Zern committed
              'Redundant blank line at the start of a code block '
              'should be deleted.')
James Zern's avatar
James Zern committed
    # Ignore blank lines at the end of a block in a long if-else
    # chain, like this:
    #   if (condition1) {
    #     // Something followed by a blank line
    #
    #   } else if (condition2) {
    #     // Something else
    #   }
    if linenum + 1 < clean_lines.NumLines():
      next_line = raw[linenum + 1]
      if (next_line
          and Match(r'\s*}', next_line)
          and next_line.find('} else ') == -1):
        error(filename, linenum, 'whitespace/blank_line', 3,
James Zern's avatar
James Zern committed
              'Redundant blank line at the end of a code block '
              'should be deleted.')

    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
  commentpos = line.find('//')
  if commentpos != -1:
    # Check if the // may be in quotes.  If so, ignore it
James Zern's avatar
James Zern committed
    # 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
      # Allow one space for new scopes, two spaces otherwise:
      if (not Match(r'^\s*{ //', line) and
          ((commentpos >= 1 and
            line[commentpos-1] not in string.whitespace) or
           (commentpos >= 2 and
            line[commentpos-2] not in string.whitespace))):
        error(filename, linenum, 'whitespace/comments', 2,
              'At least two spaces is best between code and comments')
      # There should always be a space between the // and the comment
      commentend = commentpos + 2
      if commentend < len(line) and not line[commentend] == ' ':
        # but some lines are exceptions -- e.g. if they're big
        # comment delimiters like:
        # //----------------------------------------------------------
        # or are an empty C++ style Doxygen comment, like:
        # ///
James Zern's avatar
James Zern committed
        # or C++ style Doxygen comments placed after the variable:
        # ///<  Header comment
        # //!<  Header comment
        # or they begin with multiple slashes followed by a space:
        # //////// Header comment
        match = (Search(r'[=/-]{4,}\s*$', line[commentend:]) or
                 Search(r'^/$', line[commentend:]) or
James Zern's avatar
James Zern committed
                 Search(r'^!< ', line[commentend:]) or
                 Search(r'^/< ', line[commentend:]) or
                 Search(r'^/+ ', line[commentend:]))
        if not match:
          error(filename, linenum, 'whitespace/comments', 4,
                'Should have a space between // and comment')
      CheckComment(line[commentpos:], filename, linenum, error)

  line = clean_lines.elided[linenum]  # get rid of comments and strings

  # Don't try to do spacing checks for operator methods
  line = re.sub(r'operator(==|!=|<|<<|<=|>=|>>|>)\(', 'operator\(', line)

  # We allow no-spaces around = within an if: "if ( (a=Foo()) == 0 )".
  # Otherwise not.  Note we only check for non-spaces on *both* sides;
  # sometimes people put non-spaces on one side when aligning ='s among
  # many lines (not that this is behavior that I approve of...)
  if Search(r'[\w.]=[\w.]', line) and not Search(r'\b(if|while) ', line):
    error(filename, linenum, 'whitespace/operators', 4,
          'Missing spaces around =')

  # It's ok not to have spaces around binary operators like + - * /, but if
  # there's too little whitespace, we get concerned.  It's hard to tell,
  # though, so we punt on this one for now.  TODO.

  # You should always have whitespace around binary operators.
James Zern's avatar
James Zern committed
  #
  # Check <= and >= first to avoid false positives with < and >, then
  # check non-include lines for spacing around < and >.
  match = Search(r'[^<>=!\s](==|!=|<=|>=)[^<>=!\s]', line)
  if match:
    error(filename, linenum, 'whitespace/operators', 3,
          'Missing spaces around %s' % match.group(1))
James Zern's avatar
James Zern committed
  # We allow no-spaces around << when used like this: 10<<20, but
  # not otherwise (particularly, not when used as streams)
James Zern's avatar
James Zern committed
  # Also ignore using ns::operator<<;
  match = Search(r'(operator|\S)(?:L|UL|ULL|l|ul|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) == ';')):
James Zern's avatar
James Zern committed
    error(filename, linenum, 'whitespace/operators', 3,
          'Missing spaces around <<')
  elif not Match(r'#.*include', line):
    # Avoid false positives on ->
    reduced_line = line.replace('->', '')

    # Look for < that is not surrounded by spaces.  This is only
    # triggered if both sides are missing spaces, even though
    # technically should should flag if at least one side is missing a
    # space.  This is done to avoid some false positives with shifts.
    match = Search(r'[^\s<]<([^\s=<].*)', reduced_line)
    if (match and
        not FindNextMatchingAngleBracket(clean_lines, linenum, match.group(1))):
      error(filename, linenum, 'whitespace/operators', 3,
            'Missing spaces around <')

    # Look for > that is not surrounded by spaces.  Similar to the
    # above, we only trigger if both sides are missing spaces to avoid
    # false positives with shifts.
    match = Search(r'^(.*[^\s>])>[^\s=>]', reduced_line)
    if (match and
        not FindPreviousMatchingAngleBracket(clean_lines, linenum,
                                             match.group(1))):
      error(filename, linenum, 'whitespace/operators', 3,
            'Missing spaces around >')

  # We allow no-spaces around >> for almost anything.  This is because
  # C++11 allows ">>" to close nested templates, which accounts for
  # most cases when ">>" is not followed by a space.
  #
  # We still warn on ">>" followed by alpha character, because that is
  # likely due to ">>" being used for right shifts, e.g.:
  #   value >> alpha
  #
  # When ">>" is used to close templates, the alphanumeric letter that
  # follows would be part of an identifier, and there should still be
  # a space separating the template type and the identifier.
  #   type<type<type>> alpha
  match = Search(r'>>[a-zA-Z_]', line)
  if match:
    error(filename, linenum, 'whitespace/operators', 3,
James Zern's avatar
James Zern committed
          'Missing spaces around >>')

  # There shouldn't be space around unary operators
  match = Search(r'(!\s|~\s|[\s]--[\s;]|[\s]\+\+[\s;])', line)
  if match:
    error(filename, linenum, 'whitespace/operators', 4,
          'Extra space for operator %s' % match.group(1))

  # A pet peeve of mine: no spaces after an if, while, switch, or for
  match = Search(r' (if\(|for\(|while\(|switch\()', line)
  if match:
    error(filename, linenum, 'whitespace/parens', 5,
          'Missing space before ( in %s' % match.group(1))

  # For if/for/while/switch, the left and right parens should be
  # consistent about how many spaces are inside the parens, and
  # there should either be zero or one spaces inside the parens.
  # We don't want: "if ( foo)" or "if ( foo   )".
  # Exception: "for ( ; foo; bar)" and "for (foo; bar; )" are allowed.
  match = Search(r'\b(if|for|while|switch)\s*'
                 r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$',
                 line)
  if match:
    if len(match.group(2)) != len(match.group(4)):
      if not (match.group(3) == ';' and
              len(match.group(2)) == 1 + len(match.group(4)) or
              not match.group(2) and Search(r'\bfor\s*\(.*; \)', line)):
        error(filename, linenum, 'whitespace/parens', 5,
              'Mismatching spaces inside () in %s' % match.group(1))
James Zern's avatar
James Zern committed
    if len(match.group(2)) not in [0, 1]:
      error(filename, linenum, 'whitespace/parens', 5,
            'Should have zero or one spaces inside ( and ) in %s' %
            match.group(1))

  # You should always have a space after a comma (either as fn arg or operator)
James Zern's avatar
James Zern committed
  #
  # This does not apply when the non-space character following the
  # comma is another comma, since the only time when that happens is
  # for empty macro arguments.
  #
  # We run this check in two passes: first pass on elided lines to
  # verify that lines contain missing whitespaces, second pass on raw
  # lines to confirm that those missing whitespaces are not due to
  # elided comments.
  if Search(r',[^,\s]', line) and Search(r',[^,\s]', raw[linenum]):
    error(filename, linenum, 'whitespace/comma', 3,
          '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.
  CheckSpacingForFunctionCall(filename, line, linenum, error)

  # 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.
James Zern's avatar
James Zern committed
  match = Match(r'^(.*[^ ({]){', line)
  if match:
    # Try a bit harder to check for brace initialization.  This
    # happens in one of the following forms:
    #   Constructor() : initializer_list_{} { ... }
    #   Constructor{}.MemberFunction()
    #   Type variable{};
    #   FunctionCall(type{}, ...);
    #   LastArgument(..., type{});
    #   LOG(INFO) << type{} << " ...";
    #   map_of_type[{...}] = ...;
    #
    # We check for the character following the closing brace, and
    # silence the warning if it's one of those listed above, i.e.
    # "{.;,)<]".
    #
    # To account for nested initializer list, we allow any number of
    # closing braces up to "{;,)<".  We can't simply silence the
    # warning on first sight of closing brace, because that would
    # cause false negatives for things that are not initializer lists.
    #   Silence this:         But not this:
    #     Outer{                if (...) {
    #       Inner{...}            if (...){  // Missing space before {
    #     };                    }
    #
    # 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.
    (endline, endlinenum, endpos) = CloseExpression(
        clean_lines, linenum, len(match.group(1)))
    trailing_text = ''
    if endpos > -1:
      trailing_text = endline[endpos:]
    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):
      error(filename, linenum, 'whitespace/braces', 5,
            'Missing space before {')

  # Make sure '} else {' has spaces.
  if Search(r'}else', line):
    error(filename, linenum, 'whitespace/braces', 5,
          'Missing space before else')

  # You shouldn't have spaces before your brackets, except maybe after
  # 'delete []' or 'new char * []'.
  if Search(r'\w\s+\[', line) and not Search(r'delete\s+\[', line):
    error(filename, linenum, 'whitespace/braces', 5,
          'Extra space before [')

  # You shouldn't have a space before a semicolon at the end of the line.
  # There's a special case for "for" since the style guide allows space before
  # the semicolon there.
  if Search(r':\s*;\s*$', line):
    error(filename, linenum, 'whitespace/semicolon', 5,
James Zern's avatar
James Zern committed
          'Semicolon defining empty statement. Use {} instead.')
  elif Search(r'^\s*;\s*$', line):
    error(filename, linenum, 'whitespace/semicolon', 5,
          'Line contains only semicolon. If this should be an empty statement, '
James Zern's avatar
James Zern committed
          'use {} instead.')
  elif (Search(r'\s+;\s*$', line) and
        not Search(r'\bfor\b', line)):
    error(filename, linenum, 'whitespace/semicolon', 5,
          'Extra space before last semicolon. If this should be an empty '
James Zern's avatar
James Zern committed
          'statement, use {} instead.')

  # In range-based for, we wanted spaces before and after the colon, but
  # not around "::" tokens that might appear.
  if (Search('for *\(.*[^:]:[^: ]', line) or
      Search('for *\(.*[^: ]:[^:]', line)):
    error(filename, linenum, 'whitespace/forcolon', 2,
          'Missing space around colon in range-based for loop')


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.
James Zern's avatar
James Zern committed
  if (class_info.last_line - class_info.starting_linenum <= 24 or
      linenum <= class_info.starting_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.
James Zern's avatar
James Zern committed
    # Also ignores cases where the previous line ends with a backslash as can be
    # common when defining classes in C macros.
    prev_line = clean_lines.lines[linenum - 1]
    if (not IsBlankLine(prev_line) and
James Zern's avatar
James Zern committed
        not Search(r'\b(class|struct)\b', prev_line) and
        not Search(r'\\$', 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 {
James Zern's avatar
James Zern committed
      end_class_head = class_info.starting_linenum
      for i in range(class_info.starting_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):
  """Return the most recent non-blank line and its line number.

  Args:
    clean_lines: A CleansedLines instance containing the file contents.
    linenum: The number of the line to check.

  Returns:
    A tuple with two elements.  The first element is the contents of the last
    non-blank line before the current line, or the empty string if this is the
    first non-blank line.  The second is the line number of that line, or -1
    if this is the first non-blank line.
  """

  prevlinenum = linenum - 1
  while prevlinenum >= 0:
    prevline = clean_lines.elided[prevlinenum]
    if not IsBlankLine(prevline):     # if not a blank line...
      return (prevline, prevlinenum)
    prevlinenum -= 1
  return ('', -1)


def CheckBraces(filename, clean_lines, linenum, error):
  """Looks for misplaced braces (e.g. at the end of line).

  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]        # get rid of comments and strings

  if Match(r'\s*{\s*$', line):
James Zern's avatar
James Zern committed
    # We allow an open brace to start a line in the case where someone is using
    # braces in a block to explicitly create a new scope, which is commonly used
    # to control the lifetime of stack-allocated variables.  Braces are also
    # 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.
    prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0]
James Zern's avatar
James Zern committed
    if (not Search(r'[,;:}{(]\s*$', prevline) and
James Zern's avatar
James Zern committed
        not Match(r'\s*#', prevline)):
      error(filename, linenum, 'whitespace/braces', 4,
            '{ should almost always be at the end of the previous line')

  # An else clause should be on the same line as the preceding closing brace.
  if Match(r'\s*else\s*', line):
    prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0]
    if Match(r'\s*}\s*$', prevline):
      error(filename, linenum, 'whitespace/newline', 4,
            'An else should appear on the same line as the preceding }')

  # If braces come on one side of an else, they should be on both.
  # However, we have to worry about "else if" that spans multiple lines!
  if Search(r'}\s*else[^{]*$', line) or Match(r'[^}]*else\s*{', line):
    if Search(r'}\s*else if([^{]*)$', line):       # could be multi-line if
      # find the ( after the if
      pos = line.find('else if')
      pos = line.find('(', pos)
      if pos > 0:
        (endline, _, endpos) = CloseExpression(clean_lines, linenum, pos)
        if endline[endpos:].find('{') == -1:    # must be brace after if