Ejemplo n.º 1
0
  def _ExpectSpaceBeforeOperator(self, token):
    """Returns whether a space should appear before the given operator token.

    Args:
      token: The operator token.

    Returns:
      Whether there should be a space before the token.
    """
    if token.string == ',' or token.metadata.IsUnaryPostOperator():
      return False

    if tokenutil.IsDot(token):
      return False

    # Colons should appear in labels, object literals, the case of a switch
    # statement, and ternary operator. Only want a space in the case of the
    # ternary operator.
    if self._IsLabel(token):
      return False

    if token.metadata.IsUnaryOperator() and token.IsFirstInLine():
      return False

    return True
Ejemplo n.º 2
0
  def _CheckOperator(self, token):
    """Checks an operator for spacing and line style.

    Args:
      token: The operator token.
    """
    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) and
          last_code.line_number == token.line_number):
        self._HandleError(
            errors.EXTRA_SPACE, 'Extra space before "%s"' % token.string,
            token.previous, position=Position.All(token.previous.string))

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

    # Check wrapping of operators.
    next_code = tokenutil.GetNextCodeToken(token)

    is_dot = tokenutil.IsDot(token)
    wrapped_before = last_code and last_code.line_number != token.line_number
    wrapped_after = next_code and next_code.line_number != token.line_number

    if FLAGS.dot_on_next_line and is_dot and wrapped_after:
      self._HandleError(
          errors.LINE_ENDS_WITH_DOT,
          '"." must go on the following line',
          token)
    if (not is_dot and wrapped_before and
        not token.metadata.IsUnaryOperator()):
      self._HandleError(
          errors.LINE_STARTS_WITH_OPERATOR,
          'Binary operator must go on previous line "%s"' % token.string,
          token)
Ejemplo n.º 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)
  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
    """

    # Call the base class's CheckToken function.
    super(JavaScriptLintRules, self).CheckToken(token, state)

    # Store some convenience variables
    namespaces_info = self._namespaces_info

    if error_check.ShouldCheck(Rule.UNUSED_LOCAL_VARIABLES):
      self._CheckUnusedLocalVariables(token, state)

    if error_check.ShouldCheck(Rule.UNUSED_PRIVATE_MEMBERS):
      # Find all assignments to private members.
      if token.type == Type.SIMPLE_LVALUE:
        identifier = token.string
        if identifier.endswith('_') and not identifier.endswith('__'):
          doc_comment = state.GetDocComment()
          suppressed = doc_comment and (
              'underscore' in doc_comment.suppressions or
              'unusedPrivateMembers' in doc_comment.suppressions)
          if not suppressed:
            # Look for static members defined on a provided namespace.
            if namespaces_info:
              namespace = namespaces_info.GetClosurizedNamespace(identifier)
              provided_namespaces = namespaces_info.GetProvidedNamespaces()
            else:
              namespace = None
              provided_namespaces = set()

            # Skip cases of this.something_.somethingElse_.
            regex = re.compile(r'^this\.[a-zA-Z_]+$')
            if namespace in provided_namespaces or regex.match(identifier):
              variable = identifier.split('.')[-1]
              self._declared_private_member_tokens[variable] = token
              self._declared_private_members.add(variable)
        elif not identifier.endswith('__'):
          # Consider setting public members of private members to be a usage.
          for piece in identifier.split('.'):
            if piece.endswith('_'):
              self._used_private_members.add(piece)

      # Find all usages of private members.
      if token.type == Type.IDENTIFIER:
        for piece in token.string.split('.'):
          if piece.endswith('_'):
            self._used_private_members.add(piece)

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

      if flag.flag_type == 'param' and flag.name_token is not None:
        self._CheckForMissingSpaceBeforeToken(
            token.attached_object.name_token)

        if flag.type is not None and flag.name is not None:
          if error_check.ShouldCheck(Rule.VARIABLE_ARG_MARKER):
            # Check for variable arguments marker in type.
            if flag.jstype.IsVarArgsType() and flag.name != 'var_args':
              self._HandleError(errors.JSDOC_MISSING_VAR_ARGS_NAME,
                                'Variable length argument %s must be renamed '
                                'to var_args.' % flag.name,
                                token)
            elif not flag.jstype.IsVarArgsType() and flag.name == 'var_args':
              self._HandleError(errors.JSDOC_MISSING_VAR_ARGS_TYPE,
                                'Variable length argument %s type must start '
                                'with \'...\'.' % flag.name,
                                token)

          if error_check.ShouldCheck(Rule.OPTIONAL_TYPE_MARKER):
            # Check for optional marker in type.
            if (flag.jstype.opt_arg and
                not flag.name.startswith('opt_')):
              self._HandleError(errors.JSDOC_MISSING_OPTIONAL_PREFIX,
                                'Optional parameter name %s must be prefixed '
                                'with opt_.' % flag.name,
                                token)
            elif (not flag.jstype.opt_arg and
                  flag.name.startswith('opt_')):
              self._HandleError(errors.JSDOC_MISSING_OPTIONAL_TYPE,
                                'Optional parameter %s type must end with =.' %
                                flag.name,
                                token)

      if flag.flag_type in state.GetDocFlag().HAS_TYPE:
        # Check for both missing type token and empty type braces '{}'
        # Missing suppress types are reported separately and we allow enums,
        # const, private, public and protected without types.
        if (flag.flag_type not in state.GetDocFlag().CAN_OMIT_TYPE
            and (not flag.jstype or flag.jstype.IsEmpty())):
          self._HandleError(errors.MISSING_JSDOC_TAG_TYPE,
                            'Missing type in %s tag' % token.string, token)

        elif flag.name_token and flag.type_end_token and tokenutil.Compare(
            flag.type_end_token, flag.name_token) > 0:
          self._HandleError(
              errors.OUT_OF_ORDER_JSDOC_TAG_TYPE,
              'Type should be immediately after %s tag' % token.string,
              token)

    elif token.type == Type.DOUBLE_QUOTE_STRING_START:
      next_token = token.next
      while next_token.type == Type.STRING_TEXT:
        if javascripttokenizer.JavaScriptTokenizer.SINGLE_QUOTE.search(
            next_token.string):
          break
        next_token = next_token.next
      else:
        self._HandleError(
            errors.UNNECESSARY_DOUBLE_QUOTED_STRING,
            'Single-quoted string preferred over double-quoted string.',
            token,
            position=Position.All(token.string))

    elif token.type == Type.END_DOC_COMMENT:
      doc_comment = state.GetDocComment()

      # When @externs appears in a @fileoverview comment, it should trigger
      # the same limited doc checks as a special filename like externs.js.
      if doc_comment.HasFlag('fileoverview') and doc_comment.HasFlag('externs'):
        self._SetLimitedDocChecks(True)

      if (error_check.ShouldCheck(Rule.BLANK_LINES_AT_TOP_LEVEL) and
          not self._is_html and
          state.InTopLevel() and
          not state.InNonScopeBlock()):

        # Check if we're in a fileoverview or constructor JsDoc.
        is_constructor = (
            doc_comment.HasFlag('constructor') or
            doc_comment.HasFlag('interface'))
        # @fileoverview is an optional tag so if the dosctring is the first
        # token in the file treat it as a file level docstring.
        is_file_level_comment = (
            doc_comment.HasFlag('fileoverview') or
            not doc_comment.start_token.previous)

        # If the comment is not a file overview, and it does not immediately
        # precede some code, skip it.
        # NOTE: The tokenutil methods are not used here because of their
        # behavior at the top of a file.
        next_token = token.next
        if (not next_token or
            (not is_file_level_comment and
             next_token.type in Type.NON_CODE_TYPES)):
          return

        # Don't require extra blank lines around suppression of extra
        # goog.require errors.
        if (doc_comment.SuppressionOnly() and
            next_token.type == Type.IDENTIFIER and
            next_token.string in ['goog.provide', 'goog.require']):
          return

        # Find the start of this block (include comments above the block, unless
        # this is a file overview).
        block_start = doc_comment.start_token
        if not is_file_level_comment:
          token = block_start.previous
          while token and token.type in Type.COMMENT_TYPES:
            block_start = token
            token = token.previous

        # Count the number of blank lines before this block.
        blank_lines = 0
        token = block_start.previous
        while token and token.type in [Type.WHITESPACE, Type.BLANK_LINE]:
          if token.type == Type.BLANK_LINE:
            # A blank line.
            blank_lines += 1
          elif token.type == Type.WHITESPACE and not token.line.strip():
            # A line with only whitespace on it.
            blank_lines += 1
          token = token.previous

        # Log errors.
        error_message = False
        expected_blank_lines = 0

        # Only need blank line before file overview if it is not the beginning
        # of the file, e.g. copyright is first.
        if is_file_level_comment and blank_lines == 0 and block_start.previous:
          error_message = 'Should have a blank line before a file overview.'
          expected_blank_lines = 1
        elif is_constructor and blank_lines != 3:
          error_message = (
              'Should have 3 blank lines before a constructor/interface.')
          expected_blank_lines = 3
        elif (not is_file_level_comment and not is_constructor and
              blank_lines != 2):
          error_message = 'Should have 2 blank lines between top-level blocks.'
          expected_blank_lines = 2

        if error_message:
          self._HandleError(
              errors.WRONG_BLANK_LINE_COUNT, error_message,
              block_start, position=Position.AtBeginning(),
              fix_data=expected_blank_lines - blank_lines)

    elif token.type == Type.END_BLOCK:
      if state.InFunction() and state.IsFunctionClose():
        is_immediately_called = (token.next and
                                 token.next.type == Type.START_PAREN)

        function = state.GetFunction()
        if not self._limited_doc_checks:
          if (function.has_return and function.doc and
              not is_immediately_called and
              not function.doc.HasFlag('return') and
              not function.doc.InheritsDocumentation() and
              not function.doc.HasFlag('constructor')):
            # Check for proper documentation of return value.
            self._HandleError(
                errors.MISSING_RETURN_DOCUMENTATION,
                'Missing @return JsDoc in function with non-trivial return',
                function.doc.end_token, position=Position.AtBeginning())
          elif (not function.has_return and
                not function.has_throw and
                function.doc and
                function.doc.HasFlag('return') and
                not state.InInterfaceMethod()):
            flag = function.doc.GetFlag('return')
            valid_no_return_names = ['undefined', 'void', '*']
            invalid_return = flag.jstype is None or not any(
                sub_type.identifier in valid_no_return_names
                for sub_type in flag.jstype.IterTypeGroup())

            if invalid_return:
              self._HandleError(
                  errors.UNNECESSARY_RETURN_DOCUMENTATION,
                  'Found @return JsDoc on function that returns nothing',
                  flag.flag_token, position=Position.AtBeginning())

        # b/4073735. Method in object literal definition of prototype can
        # safely reference 'this'.
        prototype_object_literal = False
        block_start = None
        previous_code = None
        previous_previous_code = None

        # Search for cases where prototype is defined as object literal.
        #       previous_previous_code
        #       |       previous_code
        #       |       | block_start
        #       |       | |
        # a.b.prototype = {
        #   c : function() {
        #     this.d = 1;
        #   }
        # }

        # If in object literal, find first token of block so to find previous
        # tokens to check above condition.
        if state.InObjectLiteral():
          block_start = state.GetCurrentBlockStart()

        # If an object literal then get previous token (code type). For above
        # case it should be '='.
        if block_start:
          previous_code = tokenutil.SearchExcept(block_start,
                                                 Type.NON_CODE_TYPES,
                                                 reverse=True)

        # If previous token to block is '=' then get its previous token.
        if previous_code and previous_code.IsOperator('='):
          previous_previous_code = tokenutil.SearchExcept(previous_code,
                                                          Type.NON_CODE_TYPES,
                                                          reverse=True)

        # If variable/token before '=' ends with '.prototype' then its above
        # case of prototype defined with object literal.
        prototype_object_literal = (previous_previous_code and
                                    previous_previous_code.string.endswith(
                                        '.prototype'))

        if (function.has_this and function.doc and
            not function.doc.HasFlag('this') and
            not function.is_constructor and
            not function.is_interface and
            '.prototype.' not in function.name and
            not prototype_object_literal):
          self._HandleError(
              errors.MISSING_JSDOC_TAG_THIS,
              'Missing @this JsDoc in function referencing "this". ('
              'this usually means you are trying to reference "this" in '
              'a static function, or you have forgotten to mark a '
              'constructor with @constructor)',
              function.doc.end_token, position=Position.AtBeginning())

    elif token.type == Type.IDENTIFIER:
      if token.string == 'goog.inherits' and not state.InFunction():
        if state.GetLastNonSpaceToken().line_number == token.line_number:
          self._HandleError(
              errors.MISSING_LINE,
              'Missing newline between constructor and goog.inherits',
              token,
              position=Position.AtBeginning())

        extra_space = state.GetLastNonSpaceToken().next
        while extra_space != token:
          if extra_space.type == Type.BLANK_LINE:
            self._HandleError(
                errors.EXTRA_LINE,
                'Extra line between constructor and goog.inherits',
                extra_space)
          extra_space = extra_space.next

        # TODO(robbyw): Test the last function was a constructor.
        # TODO(robbyw): Test correct @extends and @implements documentation.

      elif (token.string == 'goog.provide' and
            not state.InFunction() and
            namespaces_info is not None):
        namespace = tokenutil.GetStringAfterToken(token)

        # Report extra goog.provide statement.
        if not namespace or namespaces_info.IsExtraProvide(token):
          if not namespace:
            msg = 'Empty namespace in goog.provide'
          else:
            msg = 'Unnecessary goog.provide: ' +  namespace

            # Hint to user if this is a Test namespace.
            if namespace.endswith('Test'):
              msg += (' *Test namespaces must be mentioned in the '
                      'goog.setTestOnly() call')

          self._HandleError(
              errors.EXTRA_GOOG_PROVIDE,
              msg,
              token, position=Position.AtBeginning())

        if namespaces_info.IsLastProvide(token):
          # Report missing provide statements after the last existing provide.
          missing_provides = namespaces_info.GetMissingProvides()
          if missing_provides:
            self._ReportMissingProvides(
                missing_provides,
                tokenutil.GetLastTokenInSameLine(token).next,
                False)

          # If there are no require statements, missing requires should be
          # reported after the last provide.
          if not namespaces_info.GetRequiredNamespaces():
            missing_requires, illegal_alias_statements = (
                namespaces_info.GetMissingRequires())
            if missing_requires:
              self._ReportMissingRequires(
                  missing_requires,
                  tokenutil.GetLastTokenInSameLine(token).next,
                  True)
            if illegal_alias_statements:
              self._ReportIllegalAliasStatement(illegal_alias_statements)

      elif (token.string == 'goog.require' and
            not state.InFunction() and
            namespaces_info is not None):
        namespace = tokenutil.GetStringAfterToken(token)

        # If there are no provide statements, missing provides should be
        # reported before the first require.
        if (namespaces_info.IsFirstRequire(token) and
            not namespaces_info.GetProvidedNamespaces()):
          missing_provides = namespaces_info.GetMissingProvides()
          if missing_provides:
            self._ReportMissingProvides(
                missing_provides,
                tokenutil.GetFirstTokenInSameLine(token),
                True)

        # Report extra goog.require statement.
        if not namespace or namespaces_info.IsExtraRequire(token):
          if not namespace:
            msg = 'Empty namespace in goog.require'
          else:
            msg = 'Unnecessary goog.require: ' + namespace

          self._HandleError(
              errors.EXTRA_GOOG_REQUIRE,
              msg,
              token, position=Position.AtBeginning())

        # Report missing goog.require statements.
        if namespaces_info.IsLastRequire(token):
          missing_requires, illegal_alias_statements = (
              namespaces_info.GetMissingRequires())
          if missing_requires:
            self._ReportMissingRequires(
                missing_requires,
                tokenutil.GetLastTokenInSameLine(token).next,
                False)
          if illegal_alias_statements:
            self._ReportIllegalAliasStatement(illegal_alias_statements)

    elif token.type == Type.OPERATOR:
      last_in_line = token.IsLastInLine()
      # If the token is unary and appears to be used in a unary context
      # it's ok.  Otherwise, if it's at the end of the line or immediately
      # before a comment, it's ok.
      # Don't report an error before a start bracket - it will be reported
      # by that token's space checks.
      if (not token.metadata.IsUnaryOperator() and not last_in_line
          and not token.next.IsComment()
          and not token.next.IsOperator(',')
          and not tokenutil.IsDot(token)
          and token.next.type not in (Type.WHITESPACE, Type.END_PAREN,
                                      Type.END_BRACKET, Type.SEMICOLON,
                                      Type.START_BRACKET)):
        self._HandleError(
            errors.MISSING_SPACE,
            'Missing space after "%s"' % token.string,
            token,
            position=Position.AtEnd(token.string))
    elif token.type == Type.WHITESPACE:
      first_in_line = token.IsFirstInLine()
      last_in_line = token.IsLastInLine()
      # Check whitespace length if it's not the first token of the line and
      # if it's not immediately before a comment.
      if not last_in_line and not first_in_line and not token.next.IsComment():
        # Ensure there is no space after opening parentheses.
        if (token.previous.type in (Type.START_PAREN, Type.START_BRACKET,
                                    Type.FUNCTION_NAME)
            or token.next.type == Type.START_PARAMETERS):
          self._HandleError(
              errors.EXTRA_SPACE,
              'Extra space after "%s"' % token.previous.string,
              token,
              position=Position.All(token.string))
    elif token.type == Type.SEMICOLON:
      previous_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES,
                                              reverse=True)
      if not previous_token:
        self._HandleError(
            errors.REDUNDANT_SEMICOLON,
            'Semicolon without any statement',
            token,
            position=Position.AtEnd(token.string))
      elif (previous_token.type == Type.KEYWORD and
            previous_token.string not in ['break', 'continue', 'return']):
        self._HandleError(
            errors.REDUNDANT_SEMICOLON,
            ('Semicolon after \'%s\' without any statement.'
             ' Looks like an error.' % previous_token.string),
            token,
            position=Position.AtEnd(token.string))