예제 #1
0
  def GetTargetToken(self):
    """Get this comment's target token.

    Returns:
      The token that is the target of this comment, or None if there isn't one.
    """

    # File overviews describe the file, not a token.
    if self.HasFlag('fileoverview'):
      return

    skip_types = frozenset([
        Type.WHITESPACE,
        Type.BLANK_LINE,
        Type.START_PAREN])

    target_types = frozenset([
        Type.FUNCTION_NAME,
        Type.IDENTIFIER,
        Type.SIMPLE_LVALUE])

    token = self.end_token.next
    while token:
      if token.type in target_types:
        return token

      # Handles the case of a comment on "var foo = ...'
      if token.IsKeyword('var'):
        next_code_token = tokenutil.CustomSearch(
            token,
            lambda t: t.type not in Type.NON_CODE_TYPES)

        if (next_code_token and
            next_code_token.IsType(Type.SIMPLE_LVALUE)):
          return next_code_token

        return

      # Handles the case of a comment on "function foo () {}"
      if token.type is Type.FUNCTION_DECLARATION:
        next_code_token = tokenutil.CustomSearch(
            token,
            lambda t: t.type not in Type.NON_CODE_TYPES)

        if next_code_token.IsType(Type.FUNCTION_NAME):
          return next_code_token

        return

      # Skip types will end the search.
      if token.type not in skip_types:
        return

      token = token.next
    def _CheckUnusedLocalVariables(self, token, state):
        """Checks for unused local variables in function blocks.

    Args:
      token: The token to check.
      state: The state tracker.
    """
        # We don't use state.InFunction because that disregards scope functions.
        in_function = state.FunctionDepth() > 0
        if token.type == Type.SIMPLE_LVALUE or token.type == Type.IDENTIFIER:
            if in_function:
                identifier = token.string
                # Check whether the previous token was var.
                previous_code_token = tokenutil.CustomSearch(
                    token,
                    lambda t: t.type not in Type.NON_CODE_TYPES,
                    reverse=True)
                if previous_code_token and previous_code_token.IsKeyword(
                        'var'):
                    # Add local variable declaration to the top of the unused locals
                    # stack.
                    self._unused_local_variables_by_scope[-1][
                        identifier] = token
                elif token.type == Type.IDENTIFIER:
                    # This covers most cases where the variable is used as an identifier.
                    self._MarkLocalVariableUsed(token.string)
                elif token.type == Type.SIMPLE_LVALUE and '.' in identifier:
                    # This covers cases where a value is assigned to a property of the
                    # variable.
                    self._MarkLocalVariableUsed(token.string)
        elif token.type == Type.START_BLOCK:
            if in_function and state.IsFunctionOpen():
                # Push a new map onto the stack
                self._unused_local_variables_by_scope.append({})
        elif token.type == Type.END_BLOCK:
            if state.IsFunctionClose():
                # Pop the stack and report any remaining locals as unused.
                unused_local_variables = self._unused_local_variables_by_scope.pop(
                )
                for unused_token in unused_local_variables.values():
                    self._HandleError(
                        errors.UNUSED_LOCAL_VARIABLE,
                        'Unused local variable: %s.' % unused_token.string,
                        unused_token)
        elif token.type == Type.DOC_FLAG:
            # Flags that use aliased symbols should be counted.
            flag = token.attached_object
            js_type = flag and flag.jstype
            if flag and flag.flag_type in state.GetDocFlag(
            ).HAS_TYPE and js_type:
                self._MarkAliasUsed(js_type)
예제 #3
0
  def CheckToken(self, token, state):
    """Checks a token, given the current parser_state, for warnings and errors.

    Args:
      token: The current token under consideration
      state: parser_state object that indicates the current state in the page
    """
    # Store some convenience variables
    first_in_line = token.IsFirstInLine()
    last_in_line = token.IsLastInLine()
    last_non_space_token = state.GetLastNonSpaceToken()

    token_type = token.type

    # Process the line change.
    if not self._is_html and error_check.ShouldCheck(Rule.INDENTATION):
      # TODO(robbyw): Support checking indentation in HTML files.
      indentation_errors = self._indentation.CheckToken(token, state)
      for indentation_error in indentation_errors:
        self._HandleError(*indentation_error)

    if last_in_line:
      self._CheckLineLength(token, state)

    if token_type == Type.PARAMETERS:
      # Find missing spaces in parameter lists.
      if self.MISSING_PARAMETER_SPACE.search(token.string):
        fix_data = ', '.join([s.strip() for s in token.string.split(',')])
        self._HandleError(errors.MISSING_SPACE, 'Missing space after ","',
                          token, position=None, fix_data=fix_data.strip())

      # Find extra spaces at the beginning of parameter lists.  Make sure
      # we aren't at the beginning of a continuing multi-line list.
      if not first_in_line:
        space_count = len(token.string) - len(token.string.lstrip())
        if space_count:
          self._HandleError(errors.EXTRA_SPACE, 'Extra space after "("',
                            token, position=Position(0, space_count))

    elif (token_type == Type.START_BLOCK and
          token.metadata.context.type == Context.BLOCK):
      self._CheckForMissingSpaceBeforeToken(token)

    elif token_type == Type.END_BLOCK:
      last_code = token.metadata.last_code

      if FLAGS.check_trailing_comma:
        if last_code.IsOperator(','):
          self._HandleError(
              errors.COMMA_AT_END_OF_LITERAL,
              'Illegal comma at end of object literal', last_code,
              position=Position.All(last_code.string))

      if state.InFunction() and state.IsFunctionClose():
        if state.InTopLevelFunction():
          # A semicolons should not be included at the end of a function
          # declaration.
          if not state.InAssignedFunction():
            if not last_in_line and token.next.type == Type.SEMICOLON:
              self._HandleError(
                  errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION,
                  'Illegal semicolon after function declaration',
                  token.next, position=Position.All(token.next.string))

        # A semicolon should be included at the end of a function expression
        # that is not immediately called or used by a dot operator.
        if (state.InAssignedFunction() and token.next
            and token.next.type != Type.SEMICOLON):
          next_token = tokenutil.GetNextCodeToken(token)
          is_immediately_used = next_token and (
              next_token.type == Type.START_PAREN or
              tokenutil.IsDot(next_token))
          if not is_immediately_used:
            self._HandleError(
                errors.MISSING_SEMICOLON_AFTER_FUNCTION,
                'Missing semicolon after function assigned to a variable',
                token, position=Position.AtEnd(token.string))

        if state.InInterfaceMethod() and last_code.type != Type.START_BLOCK:
          self._HandleError(errors.INTERFACE_METHOD_CANNOT_HAVE_CODE,
                            'Interface methods cannot contain code', last_code)

      elif (state.IsBlockClose() and
            token.next and token.next.type == Type.SEMICOLON):
        if (last_code.metadata.context.parent.type != Context.OBJECT_LITERAL
            and last_code.metadata.context.type != Context.OBJECT_LITERAL):
          self._HandleError(
              errors.REDUNDANT_SEMICOLON,
              'No semicolon is required to end a code block',
              token.next, position=Position.All(token.next.string))

    elif token_type == Type.SEMICOLON:
      if token.previous and token.previous.type == Type.WHITESPACE:
        self._HandleError(
            errors.EXTRA_SPACE, 'Extra space before ";"',
            token.previous, position=Position.All(token.previous.string))

      if token.next and token.next.line_number == token.line_number:
        if token.metadata.context.type != Context.FOR_GROUP_BLOCK:
          # TODO(robbyw): Error about no multi-statement lines.
          pass

        elif token.next.type not in (
            Type.WHITESPACE, Type.SEMICOLON, Type.END_PAREN):
          self._HandleError(
              errors.MISSING_SPACE,
              'Missing space after ";" in for statement',
              token.next,
              position=Position.AtBeginning())

      last_code = token.metadata.last_code
      if last_code and last_code.type == Type.SEMICOLON:
        # Allow a single double semi colon in for loops for cases like:
        # for (;;) { }.
        # NOTE(user): This is not a perfect check, and will not throw an error
        # for cases like: for (var i = 0;; i < n; i++) {}, but then your code
        # probably won't work either.
        for_token = tokenutil.CustomSearch(
            last_code,
            lambda token: token.type == Type.KEYWORD and token.string == 'for',
            end_func=lambda token: token.type == Type.SEMICOLON,
            distance=None,
            reverse=True)

        if not for_token:
          self._HandleError(errors.REDUNDANT_SEMICOLON, 'Redundant semicolon',
                            token, position=Position.All(token.string))

    elif token_type == Type.START_PAREN:
      # Ensure that opening parentheses have a space before any keyword
      # that is not being invoked like a member function.
      if (token.previous and token.previous.type == Type.KEYWORD and
          (not token.previous.metadata or
           not token.previous.metadata.last_code or
           not token.previous.metadata.last_code.string or
           token.previous.metadata.last_code.string[-1:] != '.')):
        self._HandleError(errors.MISSING_SPACE, 'Missing space before "("',
                          token, position=Position.AtBeginning())
      elif token.previous and token.previous.type == Type.WHITESPACE:
        before_space = token.previous.previous
        # Ensure that there is no extra space before a function invocation,
        # even if the function being invoked happens to be a keyword.
        if (before_space and before_space.line_number == token.line_number and
            before_space.type == Type.IDENTIFIER or
            (before_space.type == Type.KEYWORD and before_space.metadata and
             before_space.metadata.last_code and
             before_space.metadata.last_code.string and
             before_space.metadata.last_code.string[-1:] == '.')):
          self._HandleError(
              errors.EXTRA_SPACE, 'Extra space before "("',
              token.previous, position=Position.All(token.previous.string))

    elif token_type == Type.START_BRACKET:
      self._HandleStartBracket(token, last_non_space_token)
    elif token_type in (Type.END_PAREN, Type.END_BRACKET):
      # Ensure there is no space before closing parentheses, except when
      # it's in a for statement with an omitted section, or when it's at the
      # beginning of a line.
      last_code = token.metadata.last_code
      if FLAGS.check_trailing_comma and last_code.IsOperator(','):
        if token_type in [Type.END_BRACKET, Type.END_PAREN]:
          self._HandleError(
              errors.COMMA_AT_END_OF_LITERAL,
              'Illegal comma at end of array literal', last_code,
              position=Position.All(last_code.string))

      if (token.previous and token.previous.type == Type.WHITESPACE and
          not token.previous.IsFirstInLine() and
          not (last_non_space_token and last_non_space_token.line_number ==
               token.line_number and
               last_non_space_token.type == Type.SEMICOLON)):
        self._HandleError(
            errors.EXTRA_SPACE, 'Extra space before "%s"' %
            token.string, token.previous,
            position=Position.All(token.previous.string))

    elif token_type == Type.WHITESPACE:
      if self.ILLEGAL_TAB.search(token.string):
        if token.IsFirstInLine():
          if token.next:
            self._HandleError(
                errors.ILLEGAL_TAB,
                'Illegal tab in whitespace before "%s"' % token.next.string,
                token, position=Position.All(token.string))
          else:
            self._HandleError(
                errors.ILLEGAL_TAB,
                'Illegal tab in whitespace',
                token, position=Position.All(token.string))
        else:
          self._HandleError(
              errors.ILLEGAL_TAB,
              'Illegal tab in whitespace after "%s"' % token.previous.string,
              token, position=Position.All(token.string))

      # Check whitespace length if it's not the first token of the line and
      # if it's not immediately before a comment.
      if last_in_line:
        # Check for extra whitespace at the end of a line.
        self._HandleError(errors.EXTRA_SPACE, 'Extra space at end of line',
                          token, position=Position.All(token.string))
      elif not first_in_line and not token.next.IsComment():
        if token.length > 1:
          self._HandleError(
              errors.EXTRA_SPACE, 'Extra space after "%s"' %
              token.previous.string, token,
              position=Position(1, len(token.string) - 1))

    elif token_type == Type.OPERATOR:
      self._CheckOperator(token)
    elif token_type == Type.DOC_FLAG:
      flag = token.attached_object

      if flag.flag_type == 'bug':
        # TODO(robbyw): Check for exactly 1 space on the left.
        string = token.next.string.lstrip()
        string = string.split(' ', 1)[0]

        if not string.isdigit():
          self._HandleError(errors.NO_BUG_NUMBER_AFTER_BUG_TAG,
                            '@bug should be followed by a bug number', token)

      elif flag.flag_type == 'suppress':
        if flag.type is None:
          # A syntactically invalid suppress tag will get tokenized as a normal
          # flag, indicating an error.
          self._HandleError(
              errors.INCORRECT_SUPPRESS_SYNTAX,
              'Invalid suppress syntax: should be @suppress {errortype}. '
              'Spaces matter.', token)
        else:
          for suppress_type in flag.jstype.IterIdentifiers():
            if suppress_type not in state.GetDocFlag().SUPPRESS_TYPES:
              self._HandleError(
                  errors.INVALID_SUPPRESS_TYPE,
                  'Invalid suppression type: %s' % suppress_type, token)

      elif (error_check.ShouldCheck(Rule.WELL_FORMED_AUTHOR) and
            flag.flag_type == 'author'):
        # TODO(user): In non strict mode check the author tag for as much as
        # it exists, though the full form checked below isn't required.
        string = token.next.string
        result = self.AUTHOR_SPEC.match(string)
        if not result:
          self._HandleError(errors.INVALID_AUTHOR_TAG_DESCRIPTION,
                            'Author tag line should be of the form: '
                            '@author [email protected] (Your Name)',
                            token.next)
        else:
          # Check spacing between email address and name. Do this before
          # checking earlier spacing so positions are easier to calculate for
          # autofixing.
          num_spaces = len(result.group(2))
          if num_spaces < 1:
            self._HandleError(errors.MISSING_SPACE,
                              'Missing space after email address',
                              token.next, position=Position(result.start(2), 0))
          elif num_spaces > 1:
            self._HandleError(
                errors.EXTRA_SPACE, 'Extra space after email address',
                token.next,
                position=Position(result.start(2) + 1, num_spaces - 1))

          # Check for extra spaces before email address. Can't be too few, if
          # not at least one we wouldn't match @author tag.
          num_spaces = len(result.group(1))
          if num_spaces > 1:
            self._HandleError(errors.EXTRA_SPACE,
                              'Extra space before email address',
                              token.next, position=Position(1, num_spaces - 1))

      elif (flag.flag_type in state.GetDocFlag().HAS_DESCRIPTION and
            not self._limited_doc_checks):
        if flag.flag_type == 'param':
          if flag.name is None:
            self._HandleError(errors.MISSING_JSDOC_PARAM_NAME,
                              'Missing name in @param tag', token)

        if not flag.description or flag.description is None:
          flag_name = token.type
          if 'name' in token.values:
            flag_name = '@' + token.values['name']

          if flag_name not in self.JSDOC_FLAGS_DESCRIPTION_NOT_REQUIRED:
            self._HandleError(
                errors.MISSING_JSDOC_TAG_DESCRIPTION,
                'Missing description in %s tag' % flag_name, token)
        else:
          self._CheckForMissingSpaceBeforeToken(flag.description_start_token)

      if flag.HasType():
        if flag.type_start_token is not None:
          self._CheckForMissingSpaceBeforeToken(
              token.attached_object.type_start_token)

        if flag.jstype and not flag.jstype.IsEmpty():
          self._CheckJsDocType(token, flag.jstype)

          if error_check.ShouldCheck(Rule.BRACES_AROUND_TYPE) and (
              flag.type_start_token.type != Type.DOC_START_BRACE or
              flag.type_end_token.type != Type.DOC_END_BRACE):
            self._HandleError(
                errors.MISSING_BRACES_AROUND_TYPE,
                'Type must always be surrounded by curly braces.', token)

    if token_type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG):
      if (token.values['name'] not in state.GetDocFlag().LEGAL_DOC and
          token.values['name'] not in FLAGS.custom_jsdoc_tags):
        self._HandleError(
            errors.INVALID_JSDOC_TAG,
            'Invalid JsDoc tag: %s' % token.values['name'], token)

      if (error_check.ShouldCheck(Rule.NO_BRACES_AROUND_INHERIT_DOC) and
          token.values['name'] == 'inheritDoc' and
          token_type == Type.DOC_INLINE_FLAG):
        self._HandleError(errors.UNNECESSARY_BRACES_AROUND_INHERIT_DOC,
                          'Unnecessary braces around @inheritDoc',
                          token)

    elif token_type == Type.SIMPLE_LVALUE:
      identifier = token.values['identifier']

      if ((not state.InFunction() or state.InConstructor()) and
          state.InTopLevel() and not state.InObjectLiteralDescendant()):
        jsdoc = state.GetDocComment()
        if not state.HasDocComment(identifier):
          # Only test for documentation on identifiers with .s in them to
          # avoid checking things like simple variables. We don't require
          # documenting assignments to .prototype itself (bug 1880803).
          if (not state.InConstructor() and
              identifier.find('.') != -1 and not
              identifier.endswith('.prototype') and not
              self._limited_doc_checks):
            comment = state.GetLastComment()
            if not (comment and comment.lower().count('jsdoc inherited')):
              self._HandleError(
                  errors.MISSING_MEMBER_DOCUMENTATION,
                  "No docs found for member '%s'" % identifier,
                  token)
        elif jsdoc and (not state.InConstructor() or
                        identifier.startswith('this.')):
          # We are at the top level and the function/member is documented.
          if identifier.endswith('_') and not identifier.endswith('__'):
            # Can have a private class which inherits documentation from a
            # public superclass.
            #
            # @inheritDoc is deprecated in favor of using @override, and they
            if (jsdoc.HasFlag('override') and not jsdoc.HasFlag('constructor')
                and ('accessControls' not in jsdoc.suppressions)):
              self._HandleError(
                  errors.INVALID_OVERRIDE_PRIVATE,
                  '%s should not override a private member.' % identifier,
                  jsdoc.GetFlag('override').flag_token)
            if (jsdoc.HasFlag('inheritDoc') and not jsdoc.HasFlag('constructor')
                and ('accessControls' not in jsdoc.suppressions)):
              self._HandleError(
                  errors.INVALID_INHERIT_DOC_PRIVATE,
                  '%s should not inherit from a private member.' % identifier,
                  jsdoc.GetFlag('inheritDoc').flag_token)
            if (not jsdoc.HasFlag('private') and
                ('underscore' not in jsdoc.suppressions) and not
                ((jsdoc.HasFlag('inheritDoc') or jsdoc.HasFlag('override')) and
                 ('accessControls' in jsdoc.suppressions))):
              self._HandleError(
                  errors.MISSING_PRIVATE,
                  'Member "%s" must have @private JsDoc.' %
                  identifier, token)
            if jsdoc.HasFlag('private') and 'underscore' in jsdoc.suppressions:
              self._HandleError(
                  errors.UNNECESSARY_SUPPRESS,
                  '@suppress {underscore} is not necessary with @private',
                  jsdoc.suppressions['underscore'])
          elif (jsdoc.HasFlag('private') and
                not self.InExplicitlyTypedLanguage()):
            # It is convention to hide public fields in some ECMA
            # implementations from documentation using the @private tag.
            self._HandleError(
                errors.EXTRA_PRIVATE,
                'Member "%s" must not have @private JsDoc' %
                identifier, token)

          # These flags are only legal on localizable message definitions;
          # such variables always begin with the prefix MSG_.
          if not identifier.startswith('MSG_') and '.MSG_' not in identifier:
            for f in ('desc', 'hidden', 'meaning'):
              if jsdoc.HasFlag(f):
                self._HandleError(
                    errors.INVALID_USE_OF_DESC_TAG,
                    'Member "%s" does not start with MSG_ and thus '
                    'should not have @%s JsDoc' % (identifier, f),
                    token)

      # Check for illegaly assigning live objects as prototype property values.
      index = identifier.find('.prototype.')
      # Ignore anything with additional .s after the prototype.
      if index != -1 and identifier.find('.', index + 11) == -1:
        equal_operator = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
        next_code = tokenutil.SearchExcept(equal_operator, Type.NON_CODE_TYPES)
        if next_code and (
            next_code.type in (Type.START_BRACKET, Type.START_BLOCK) or
            next_code.IsOperator('new')):
          self._HandleError(
              errors.ILLEGAL_PROTOTYPE_MEMBER_VALUE,
              'Member %s cannot have a non-primitive value' % identifier,
              token)

    elif token_type == Type.END_PARAMETERS:
      # Find extra space at the end of parameter lists.  We check the token
      # prior to the current one when it is a closing paren.
      if (token.previous and token.previous.type == Type.PARAMETERS
          and self.ENDS_WITH_SPACE.search(token.previous.string)):
        self._HandleError(errors.EXTRA_SPACE, 'Extra space before ")"',
                          token.previous)

      jsdoc = state.GetDocComment()
      if state.GetFunction().is_interface:
        if token.previous and token.previous.type == Type.PARAMETERS:
          self._HandleError(
              errors.INTERFACE_CONSTRUCTOR_CANNOT_HAVE_PARAMS,
              'Interface constructor cannot have parameters',
              token.previous)
      elif (state.InTopLevel() and jsdoc and not jsdoc.HasFlag('see')
            and not jsdoc.InheritsDocumentation()
            and not state.InObjectLiteralDescendant() and not
            jsdoc.IsInvalidated()):
        distance, edit = jsdoc.CompareParameters(state.GetParams())
        if distance:
          params_iter = iter(state.GetParams())
          docs_iter = iter(jsdoc.ordered_params)

          for op in edit:
            if op == 'I':
              # Insertion.
              # Parsing doc comments is the same for all languages
              # but some languages care about parameters that don't have
              # doc comments and some languages don't care.
              # Languages that don't allow variables to by typed such as
              # JavaScript care but languages such as ActionScript or Java
              # that allow variables to be typed don't care.
              if not self._limited_doc_checks:
                self.HandleMissingParameterDoc(token, params_iter.next())

            elif op == 'D':
              # Deletion
              self._HandleError(errors.EXTRA_PARAMETER_DOCUMENTATION,
                                'Found docs for non-existing parameter: "%s"' %
                                docs_iter.next(), token)
            elif op == 'S':
              # Substitution
              if not self._limited_doc_checks:
                self._HandleError(
                    errors.WRONG_PARAMETER_DOCUMENTATION,
                    'Parameter mismatch: got "%s", expected "%s"' %
                    (params_iter.next(), docs_iter.next()), token)

            else:
              # Equality - just advance the iterators
              params_iter.next()
              docs_iter.next()

    elif token_type == Type.STRING_TEXT:
      # If this is the first token after the start of the string, but it's at
      # the end of a line, we know we have a multi-line string.
      if token.previous.type in (
          Type.SINGLE_QUOTE_STRING_START,
          Type.DOUBLE_QUOTE_STRING_START) and last_in_line:
        self._HandleError(errors.MULTI_LINE_STRING,
                          'Multi-line strings are not allowed', token)

    # This check is orthogonal to the ones above, and repeats some types, so
    # it is a plain if and not an elif.
    if token.type in Type.COMMENT_TYPES:
      if self.ILLEGAL_TAB.search(token.string):
        self._HandleError(errors.ILLEGAL_TAB,
                          'Illegal tab in comment "%s"' % token.string, token)

      trimmed = token.string.rstrip()
      if last_in_line and token.string != trimmed:
        # Check for extra whitespace at the end of a line.
        self._HandleError(
            errors.EXTRA_SPACE, 'Extra space at end of line', token,
            position=Position(len(trimmed), len(token.string) - len(trimmed)))

    # This check is also orthogonal since it is based on metadata.
    if token.metadata.is_implied_semicolon:
      self._HandleError(errors.MISSING_SEMICOLON,
                        'Missing semicolon at end of line', token)
예제 #4
0
    def CheckToken(self, token, state):
        """Checks a token, given the current parser_state, for warnings and errors.

    Args:
      token: The current token under consideration
      state: parser_state object that indicates the current state in the page
    """
        # Store some convenience variables
        first_in_line = token.IsFirstInLine()
        last_in_line = token.IsLastInLine()
        last_non_space_token = state.GetLastNonSpaceToken()

        type = token.type

        # Process the line change.
        if not self._is_html and FLAGS.strict:
            # TODO(robbyw): Support checking indentation in HTML files.
            indentation_errors = self._indentation.CheckToken(token, state)
            for indentation_error in indentation_errors:
                self._HandleError(*indentation_error)

        if last_in_line:
            self._CheckLineLength(token, state)

        if type == Type.PARAMETERS:
            # Find missing spaces in parameter lists.
            if self.MISSING_PARAMETER_SPACE.search(token.string):
                self._HandleError(errors.MISSING_SPACE,
                                  'Missing space after ","', token)

            # Find extra spaces at the beginning of parameter lists.  Make sure
            # we aren't at the beginning of a continuing multi-line list.
            if not first_in_line:
                space_count = len(token.string) - len(token.string.lstrip())
                if space_count:
                    self._HandleError(errors.EXTRA_SPACE,
                                      'Extra space after "("', token,
                                      Position(0, space_count))

        elif (type == Type.START_BLOCK
              and token.metadata.context.type == Context.BLOCK):
            self._CheckForMissingSpaceBeforeToken(token)

        elif type == Type.END_BLOCK:
            # This check is for object literal end block tokens, but there is no need
            # to test that condition since a comma at the end of any other kind of
            # block is undoubtedly a parse error.
            last_code = token.metadata.last_code
            if last_code.IsOperator(','):
                self._HandleError(errors.COMMA_AT_END_OF_LITERAL,
                                  'Illegal comma at end of object literal',
                                  last_code, Position.All(last_code.string))

            if state.InFunction() and state.IsFunctionClose():
                is_immediately_called = (token.next and token.next.type
                                         == Type.START_PAREN)
                if state.InTopLevelFunction():
                    # When the function was top-level and not immediately called, check
                    # that it's terminated by a semi-colon.
                    if state.InAssignedFunction():
                        if not is_immediately_called and (
                                last_in_line
                                or not token.next.type == Type.SEMICOLON):
                            self._HandleError(
                                errors.MISSING_SEMICOLON_AFTER_FUNCTION,
                                'Missing semicolon after function assigned to a variable',
                                token, Position.AtEnd(token.string))
                    else:
                        if not last_in_line and token.next.type == Type.SEMICOLON:
                            self._HandleError(
                                errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION,
                                'Illegal semicolon after function declaration',
                                token.next, Position.All(token.next.string))

                if (state.InInterfaceMethod()
                        and last_code.type != Type.START_BLOCK):
                    self._HandleError(errors.INTERFACE_METHOD_CANNOT_HAVE_CODE,
                                      'Interface methods cannot contain code',
                                      last_code)

            elif (state.IsBlockClose() and token.next
                  and token.next.type == Type.SEMICOLON):
                self._HandleError(
                    errors.REDUNDANT_SEMICOLON,
                    'No semicolon is required to end a code block', token.next,
                    Position.All(token.next.string))

        elif type == Type.SEMICOLON:
            if token.previous and token.previous.type == Type.WHITESPACE:
                self._HandleError(errors.EXTRA_SPACE, 'Extra space before ";"',
                                  token.previous,
                                  Position.All(token.previous.string))

            if token.next and token.next.line_number == token.line_number:
                if token.metadata.context.type != Context.FOR_GROUP_BLOCK:
                    # TODO(robbyw): Error about no multi-statement lines.
                    pass

                elif token.next.type not in (Type.WHITESPACE, Type.SEMICOLON,
                                             Type.END_PAREN):
                    self._HandleError(
                        errors.MISSING_SPACE,
                        'Missing space after ";" in for statement', token.next,
                        Position.AtBeginning())

            last_code = token.metadata.last_code
            if last_code and last_code.type == Type.SEMICOLON:
                # Allow a single double semi colon in for loops for cases like:
                # for (;;) { }.
                # NOTE(user): This is not a perfect check, and will not throw an error
                # for cases like: for (var i = 0;; i < n; i++) {}, but then your code
                # probably won't work either.
                for_token = tokenutil.CustomSearch(
                    last_code,
                    lambda token: token.type == Type.KEYWORD and token.string
                    == 'for',
                    end_func=lambda token: token.type == Type.SEMICOLON,
                    distance=None,
                    reverse=True)

                if not for_token:
                    self._HandleError(errors.REDUNDANT_SEMICOLON,
                                      'Redundant semicolon', token,
                                      Position.All(token.string))

        elif type == Type.START_PAREN:
            if token.previous and token.previous.type == Type.KEYWORD:
                self._HandleError(errors.MISSING_SPACE,
                                  'Missing space before "("', token,
                                  Position.AtBeginning())
            elif token.previous and token.previous.type == Type.WHITESPACE:
                before_space = token.previous.previous
                if (before_space
                        and before_space.line_number == token.line_number
                        and before_space.type == Type.IDENTIFIER):
                    self._HandleError(errors.EXTRA_SPACE,
                                      'Extra space before "("', token.previous,
                                      Position.All(token.previous.string))

        elif type == Type.START_BRACKET:
            if (not first_in_line and token.previous.type == Type.WHITESPACE
                    and last_non_space_token and last_non_space_token.type
                    in Type.EXPRESSION_ENDER_TYPES):
                self._HandleError(errors.EXTRA_SPACE, 'Extra space before "["',
                                  token.previous,
                                  Position.All(token.previous.string))
            # If the [ token is the first token in a line we shouldn't complain
            # about a missing space before [.  This is because some Ecma script
            # languages allow syntax like:
            # [Annotation]
            # class MyClass {...}
            # So we don't want to blindly warn about missing spaces before [.
            # In the the future, when rules for computing exactly how many spaces
            # lines should be indented are added, then we can return errors for
            # [ tokens that are improperly indented.
            # For example:
            # var someVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVariableName =
            # [a,b,c];
            # should trigger a proper indentation warning message as [ is not indented
            # by four spaces.
            elif (not first_in_line and token.previous
                  and not token.previous.type
                  in ([Type.WHITESPACE, Type.START_PAREN, Type.START_BRACKET] +
                      Type.EXPRESSION_ENDER_TYPES)):
                self._HandleError(errors.MISSING_SPACE,
                                  'Missing space before "["', token,
                                  Position.AtBeginning())

        elif type in (Type.END_PAREN, Type.END_BRACKET):
            # Ensure there is no space before closing parentheses, except when
            # it's in a for statement with an omitted section, or when it's at the
            # beginning of a line.
            if (token.previous and token.previous.type == Type.WHITESPACE
                    and not token.previous.IsFirstInLine() and
                    not (last_non_space_token and
                         last_non_space_token.line_number == token.line_number
                         and last_non_space_token.type == Type.SEMICOLON)):
                self._HandleError(errors.EXTRA_SPACE,
                                  'Extra space before "%s"' % token.string,
                                  token.previous,
                                  Position.All(token.previous.string))

            if token.type == Type.END_BRACKET:
                last_code = token.metadata.last_code
                if last_code.IsOperator(','):
                    self._HandleError(errors.COMMA_AT_END_OF_LITERAL,
                                      'Illegal comma at end of array literal',
                                      last_code,
                                      Position.All(last_code.string))

        elif type == Type.WHITESPACE:
            if self.ILLEGAL_TAB.search(token.string):
                if token.IsFirstInLine():
                    self._HandleError(
                        errors.ILLEGAL_TAB,
                        'Illegal tab in whitespace before "%s"' %
                        token.next.string, token, Position.All(token.string))
                else:
                    self._HandleError(
                        errors.ILLEGAL_TAB,
                        'Illegal tab in whitespace after "%s"' %
                        token.previous.string, token,
                        Position.All(token.string))

            # Check whitespace length if it's not the first token of the line and
            # if it's not immediately before a comment.
            if last_in_line:
                # Check for extra whitespace at the end of a line.
                self._HandleError(errors.EXTRA_SPACE,
                                  'Extra space at end of line', token,
                                  Position.All(token.string))
            elif not first_in_line and not token.next.IsComment():
                if token.length > 1:
                    self._HandleError(
                        errors.EXTRA_SPACE,
                        'Extra space after "%s"' % token.previous.string,
                        token, Position(1,
                                        len(token.string) - 1))

        elif type == Type.OPERATOR:
            last_code = token.metadata.last_code

            if not self._ExpectSpaceBeforeOperator(token):
                if (token.previous and token.previous.type == Type.WHITESPACE
                        and last_code
                        and last_code.type in (Type.NORMAL, Type.IDENTIFIER)):
                    self._HandleError(errors.EXTRA_SPACE,
                                      'Extra space before "%s"' % token.string,
                                      token.previous,
                                      Position.All(token.previous.string))

            elif (token.previous and not token.previous.IsComment()
                  and token.previous.type in Type.EXPRESSION_ENDER_TYPES):
                self._HandleError(errors.MISSING_SPACE,
                                  'Missing space before "%s"' % token.string,
                                  token, Position.AtBeginning())

            # Check that binary operators are not used to start lines.
            if ((not last_code or last_code.line_number != token.line_number)
                    and not token.metadata.IsUnaryOperator()):
                self._HandleError(
                    errors.LINE_STARTS_WITH_OPERATOR,
                    'Binary operator should go on previous line "%s"' %
                    token.string, token)

        elif type == Type.DOC_FLAG:
            flag = token.attached_object

            if flag.flag_type == 'bug':
                # TODO(robbyw): Check for exactly 1 space on the left.
                string = token.next.string.lstrip()
                string = string.split(' ', 1)[0]

                if not string.isdigit():
                    self._HandleError(
                        errors.NO_BUG_NUMBER_AFTER_BUG_TAG,
                        '@bug should be followed by a bug number', token)

            elif flag.flag_type == 'suppress':
                if flag.type is None:
                    # A syntactically invalid suppress tag will get tokenized as a normal
                    # flag, indicating an error.
                    self._HandleError(
                        errors.INCORRECT_SUPPRESS_SYNTAX,
                        'Invalid suppress syntax: should be @suppress {errortype}. '
                        'Spaces matter.', token)
                elif flag.type not in state.GetDocFlag().SUPPRESS_TYPES:
                    self._HandleError(
                        errors.INVALID_SUPPRESS_TYPE,
                        'Invalid suppression type: %s' % flag.type, token)

            elif FLAGS.strict and flag.flag_type == 'author':
                # TODO(user): In non strict mode check the author tag for as much as
                # it exists, though the full form checked below isn't required.
                string = token.next.string
                result = self.AUTHOR_SPEC.match(string)
                if not result:
                    self._HandleError(
                        errors.INVALID_AUTHOR_TAG_DESCRIPTION,
                        'Author tag line should be of the form: '
                        '@author [email protected] (Your Name)', token.next)
                else:
                    # Check spacing between email address and name. Do this before
                    # checking earlier spacing so positions are easier to calculate for
                    # autofixing.
                    num_spaces = len(result.group(2))
                    if num_spaces < 1:
                        self._HandleError(errors.MISSING_SPACE,
                                          'Missing space after email address',
                                          token.next,
                                          Position(result.start(2), 0))
                    elif num_spaces > 1:
                        self._HandleError(
                            errors.EXTRA_SPACE,
                            'Extra space after email address', token.next,
                            Position(result.start(2) + 1, num_spaces - 1))

                    # Check for extra spaces before email address. Can't be too few, if
                    # not at least one we wouldn't match @author tag.
                    num_spaces = len(result.group(1))
                    if num_spaces > 1:
                        self._HandleError(errors.EXTRA_SPACE,
                                          'Extra space before email address',
                                          token.next,
                                          Position(1, num_spaces - 1))

            elif (flag.flag_type in state.GetDocFlag().HAS_DESCRIPTION
                  and not self._limited_doc_checks):
                if flag.flag_type == 'param':
                    if flag.name is None:
                        self._HandleError(errors.MISSING_JSDOC_PARAM_NAME,
                                          'Missing name in @param tag', token)

                if not flag.description or flag.description is None:
                    flag_name = token.type
                    if 'name' in token.values:
                        flag_name = '@' + token.values['name']
                    self._HandleError(
                        errors.MISSING_JSDOC_TAG_DESCRIPTION,
                        'Missing description in %s tag' % flag_name, token)
                else:
                    self._CheckForMissingSpaceBeforeToken(
                        flag.description_start_token)

                    # We want punctuation to be inside of any tags ending a description,
                    # so strip tags before checking description. See bug 1127192. Note
                    # that depending on how lines break, the real description end token
                    # may consist only of stripped html and the effective end token can
                    # be different.
                    end_token = flag.description_end_token
                    end_string = htmlutil.StripTags(end_token.string).strip()
                    while (end_string == ''
                           and not end_token.type in Type.FLAG_ENDING_TYPES):
                        end_token = end_token.previous
                        if end_token.type in Type.FLAG_DESCRIPTION_TYPES:
                            end_string = htmlutil.StripTags(
                                end_token.string).rstrip()

                    if not (end_string.endswith('.')
                            or end_string.endswith('?')
                            or end_string.endswith('!')):
                        # Find the position for the missing punctuation, inside of any html
                        # tags.
                        desc_str = end_token.string.rstrip()
                        while desc_str.endswith('>'):
                            start_tag_index = desc_str.rfind('<')
                            if start_tag_index < 0:
                                break
                            desc_str = desc_str[:start_tag_index].rstrip()
                        end_position = Position(len(desc_str), 0)

                        self._HandleError(
                            errors.
                            JSDOC_TAG_DESCRIPTION_ENDS_WITH_INVALID_CHARACTER,
                            ('%s descriptions must end with valid punctuation such as a '
                             'period.' % token.string), end_token,
                            end_position)

            if flag.flag_type in state.GetDocFlag().HAS_TYPE:
                if flag.type_start_token is not None:
                    self._CheckForMissingSpaceBeforeToken(
                        token.attached_object.type_start_token)

                if flag.type and flag.type != '' and not flag.type.isspace():
                    self._CheckJsDocType(token)

        if type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG):
            if (token.values['name'] not in state.GetDocFlag().LEGAL_DOC
                    and token.values['name'] not in FLAGS.custom_jsdoc_tags):
                self._HandleError(
                    errors.INVALID_JSDOC_TAG,
                    'Invalid JsDoc tag: %s' % token.values['name'], token)

            if (FLAGS.strict and token.values['name'] == 'inheritDoc'
                    and type == Type.DOC_INLINE_FLAG):
                self._HandleError(errors.UNNECESSARY_BRACES_AROUND_INHERIT_DOC,
                                  'Unnecessary braces around @inheritDoc',
                                  token)

        elif type == Type.SIMPLE_LVALUE:
            identifier = token.values['identifier']

            if ((not state.InFunction() or state.InConstructor())
                    and not state.InParentheses()
                    and not state.InObjectLiteralDescendant()):
                jsdoc = state.GetDocComment()
                if not state.HasDocComment(identifier):
                    # Only test for documentation on identifiers with .s in them to
                    # avoid checking things like simple variables. We don't require
                    # documenting assignments to .prototype itself (bug 1880803).
                    if (not state.InConstructor()
                            and identifier.find('.') != -1
                            and not identifier.endswith('.prototype')
                            and not self._limited_doc_checks):
                        comment = state.GetLastComment()
                        if not (comment
                                and comment.lower().count('jsdoc inherited')):
                            self._HandleError(
                                errors.MISSING_MEMBER_DOCUMENTATION,
                                "No docs found for member '%s'" % identifier,
                                token)
                elif jsdoc and (not state.InConstructor()
                                or identifier.startswith('this.')):
                    # We are at the top level and the function/member is documented.
                    if identifier.endswith(
                            '_') and not identifier.endswith('__'):
                        if jsdoc.HasFlag('override'):
                            self._HandleError(
                                errors.INVALID_OVERRIDE_PRIVATE,
                                '%s should not override a private member.' %
                                identifier,
                                jsdoc.GetFlag('override').flag_token)
                        # Can have a private class which inherits documentation from a
                        # public superclass.
                        if jsdoc.HasFlag('inheritDoc') and not jsdoc.HasFlag(
                                'constructor'):
                            self._HandleError(
                                errors.INVALID_INHERIT_DOC_PRIVATE,
                                '%s should not inherit from a private member.'
                                % identifier,
                                jsdoc.GetFlag('inheritDoc').flag_token)
                        if (not jsdoc.HasFlag('private')
                                and not ('underscore' in jsdoc.suppressions)):
                            self._HandleError(
                                errors.MISSING_PRIVATE,
                                'Member "%s" must have @private JsDoc.' %
                                identifier, token)
                        if jsdoc.HasFlag(
                                'private'
                        ) and 'underscore' in jsdoc.suppressions:
                            self._HandleError(
                                errors.UNNECESSARY_SUPPRESS,
                                '@suppress {underscore} is not necessary with @private',
                                jsdoc.suppressions['underscore'])
                    elif jsdoc.HasFlag('private'):
                        self._HandleError(
                            errors.EXTRA_PRIVATE,
                            'Member "%s" must not have @private JsDoc' %
                            identifier, token)

                    if ((jsdoc.HasFlag('desc') or jsdoc.HasFlag('hidden'))
                            and not identifier.startswith('MSG_')
                            and identifier.find('.MSG_') == -1):
                        # TODO(user): Update error message to show the actual invalid
                        # tag, either @desc or @hidden.
                        self._HandleError(
                            errors.INVALID_USE_OF_DESC_TAG,
                            'Member "%s" should not have @desc JsDoc' %
                            identifier, token)

            # Check for illegaly assigning live objects as prototype property values.
            index = identifier.find('.prototype.')
            # Ignore anything with additional .s after the prototype.
            if index != -1 and identifier.find('.', index + 11) == -1:
                equal_operator = tokenutil.SearchExcept(
                    token, Type.NON_CODE_TYPES)
                next_code = tokenutil.SearchExcept(equal_operator,
                                                   Type.NON_CODE_TYPES)
                if next_code and (next_code.type
                                  in (Type.START_BRACKET, Type.START_BLOCK)
                                  or next_code.IsOperator('new')):
                    self._HandleError(
                        errors.ILLEGAL_PROTOTYPE_MEMBER_VALUE,
                        'Member %s cannot have a non-primitive value' %
                        identifier, token)

        elif type == Type.END_PARAMETERS:
            # Find extra space at the end of parameter lists.  We check the token
            # prior to the current one when it is a closing paren.
            if (token.previous and token.previous.type == Type.PARAMETERS
                    and self.ENDS_WITH_SPACE.search(token.previous.string)):
                self._HandleError(errors.EXTRA_SPACE, 'Extra space before ")"',
                                  token.previous)

            jsdoc = state.GetDocComment()
            if state.GetFunction().is_interface:
                if token.previous and token.previous.type == Type.PARAMETERS:
                    self._HandleError(
                        errors.INTERFACE_CONSTRUCTOR_CANNOT_HAVE_PARAMS,
                        'Interface constructor cannot have parameters',
                        token.previous)
            elif (state.InTopLevel() and jsdoc and not jsdoc.HasFlag('see')
                  and not jsdoc.InheritsDocumentation()
                  and not state.InObjectLiteralDescendant()
                  and not jsdoc.IsInvalidated()):
                distance, edit = jsdoc.CompareParameters(state.GetParams())
                if distance:
                    params_iter = iter(state.GetParams())
                    docs_iter = iter(jsdoc.ordered_params)

                    for op in edit:
                        if op == 'I':
                            # Insertion.
                            # Parsing doc comments is the same for all languages
                            # but some languages care about parameters that don't have
                            # doc comments and some languages don't care.
                            # Languages that don't allow variables to by typed such as
                            # JavaScript care but languages such as ActionScript or Java
                            # that allow variables to be typed don't care.
                            if not self._limited_doc_checks:
                                self.HandleMissingParameterDoc(
                                    token, params_iter.next())

                        elif op == 'D':
                            # Deletion
                            self._HandleError(
                                errors.EXTRA_PARAMETER_DOCUMENTATION,
                                'Found docs for non-existing parameter: "%s"' %
                                docs_iter.next(), token)
                        elif op == 'S':
                            # Substitution
                            if not self._limited_doc_checks:
                                self._HandleError(
                                    errors.WRONG_PARAMETER_DOCUMENTATION,
                                    'Parameter mismatch: got "%s", expected "%s"'
                                    % (params_iter.next(), docs_iter.next()),
                                    token)

                        else:
                            # Equality - just advance the iterators
                            params_iter.next()
                            docs_iter.next()

        elif type == Type.STRING_TEXT:
            # If this is the first token after the start of the string, but it's at
            # the end of a line, we know we have a multi-line string.
            if token.previous.type in (
                    Type.SINGLE_QUOTE_STRING_START,
                    Type.DOUBLE_QUOTE_STRING_START) and last_in_line:
                self._HandleError(errors.MULTI_LINE_STRING,
                                  'Multi-line strings are not allowed', token)

        # This check is orthogonal to the ones above, and repeats some types, so
        # it is a plain if and not an elif.
        if token.type in Type.COMMENT_TYPES:
            if self.ILLEGAL_TAB.search(token.string):
                self._HandleError(errors.ILLEGAL_TAB,
                                  'Illegal tab in comment "%s"' % token.string,
                                  token)

            trimmed = token.string.rstrip()
            if last_in_line and token.string != trimmed:
                # Check for extra whitespace at the end of a line.
                self._HandleError(
                    errors.EXTRA_SPACE, 'Extra space at end of line', token,
                    Position(len(trimmed),
                             len(token.string) - len(trimmed)))

        # This check is also orthogonal since it is based on metadata.
        if token.metadata.is_implied_semicolon:
            self._HandleError(errors.MISSING_SEMICOLON,
                              'Missing semicolon at end of line', token)