Exemplo n.º 1
0
 def IsTypeToken(self, t):
   if self.InDocComment() and t.type not in (Type.START_DOC_COMMENT,
       Type.DOC_FLAG, Type.DOC_INLINE_FLAG, Type.DOC_PREFIX):
     f = tokenutil.SearchUntil(t, [Type.DOC_FLAG], [Type.START_DOC_COMMENT],
                               None, True)
     if f and f.attached_object.type_start_token is not None:
       return (tokenutil.Compare(t, f.attached_object.type_start_token) > 0 and
               tokenutil.Compare(t, f.attached_object.type_end_token) < 0)
   return False
    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
    """

        # For @param don't ignore record type.
        if (self.__ContainsRecordType(token)
                and not token.attached_object.flag_type == 'param'):
            # We should bail out and not emit any warnings for this annotation.
            # TODO(nicksantos): Support record types for real.
            state.GetDocComment().Invalidate()
            return

        # 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 doc_comment.HasFlag('suppress')
                                  and doc_comment.GetFlag('suppress').type
                                  == 'underscore')
                    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('^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.type.startswith('...')
                                and not 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.type.startswith('...')
                              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.type.endswith('=')
                                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.type.endswith('=')
                              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
                # without types.
                if (flag.flag_type not in ('suppress', 'enum')
                        and (not flag.type or flag.type.isspace())):
                    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.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'))
                is_file_overview = doc_comment.HasFlag('fileoverview')

                # 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_overview
                            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_overview:
                    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_overview 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_overview 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.AtBeginning(),
                                      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.AtBeginning())
                    elif (not function.has_return and not function.has_throw
                          and function.doc and function.doc.HasFlag('return')
                          and not state.InInterfaceMethod()):
                        return_flag = function.doc.GetFlag('return')
                        if (return_flag.type is None
                                or ('undefined' not in return_flag.type
                                    and 'void' not in return_flag.type
                                    and '*' not in return_flag.type)):
                            self._HandleError(
                                errors.UNNECESSARY_RETURN_DOCUMENTATION,
                                'Found @return JsDoc on function that returns nothing',
                                return_flag.flag_token, 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, None, 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, None, 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.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.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.Search(token, Type.STRING_TEXT).string

                # Report extra goog.provide statement.
                if namespaces_info.IsExtraProvide(token):
                    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 = namespaces_info.GetMissingRequires()
                        if missing_requires:
                            self._ReportMissingRequires(
                                missing_requires,
                                tokenutil.GetLastTokenInSameLine(token).next,
                                True)

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

                # 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 namespaces_info.IsExtraRequire(token):

                    self._HandleError(errors.EXTRA_GOOG_REQUIRE,
                                      'Unnecessary goog.require: ' + namespace,
                                      token,
                                      position=Position.AtBeginning())

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

        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 token.next.type
                    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.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.All(token.string))
        elif token.type == Type.SEMICOLON:
            previous_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES,
                                                    None, True)
            if (previous_token.type == Type.KEYWORD
                    and not previous_token.string
                    in ['break', 'continue', 'return']):
                self._HandleError(
                    errors.REDUNDANT_SEMICOLON,
                    ('Semicolon after \'%s\' without any statement.'
                     ' Looks like an error.' % previous_token.string), token,
                    Position.AtEnd(token.string))
Exemplo n.º 3
0
    def __init__(self, flag_token):
        """Creates the DocFlag object and attaches it to the given start token.

    Args:
      flag_token: The starting token of the flag.
    """
        self.flag_token = flag_token
        self.flag_type = flag_token.string.strip().lstrip('@')

        # Extract type, if applicable.
        self.type = None
        self.type_start_token = None
        self.type_end_token = None
        if self.flag_type in self.HAS_TYPE:
            brace = tokenutil.SearchUntil(flag_token, [Type.DOC_START_BRACE],
                                          Type.FLAG_ENDING_TYPES)
            if brace:
                end_token, contents = _GetMatchingEndBraceAndContents(brace)
                self.type = contents
                self.type_start_token = brace
                self.type_end_token = end_token
            elif (self.flag_type in self.TYPE_ONLY
                  and flag_token.next.type not in Type.FLAG_ENDING_TYPES
                  and flag_token.line_number == flag_token.next.line_number):
                # b/10407058. If the flag is expected to be followed by a type then
                # search for type in same line only. If no token after flag in same
                # line then conclude that no type is specified.
                self.type_start_token = flag_token.next
                self.type_end_token, self.type = _GetEndTokenAndContents(
                    self.type_start_token)
                if self.type is not None:
                    self.type = self.type.strip()

        # Extract name, if applicable.
        self.name_token = None
        self.name = None
        if self.flag_type in self.HAS_NAME:
            # Handle bad case, name could be immediately after flag token.
            self.name_token = _GetNextPartialIdentifierToken(flag_token)

            # Handle good case, if found token is after type start, look for
            # a identifier (substring to cover cases like [cnt] b/4197272) after
            # type end, since types contain identifiers.
            if (self.type and self.name_token and tokenutil.Compare(
                    self.name_token, self.type_start_token) > 0):
                self.name_token = _GetNextPartialIdentifierToken(
                    self.type_end_token)

            if self.name_token:
                self.name = self.name_token.string

        # Extract description, if applicable.
        self.description_start_token = None
        self.description_end_token = None
        self.description = None
        if self.flag_type in self.HAS_DESCRIPTION:
            search_start_token = flag_token
            if self.name_token and self.type_end_token:
                if tokenutil.Compare(self.type_end_token, self.name_token) > 0:
                    search_start_token = self.type_end_token
                else:
                    search_start_token = self.name_token
            elif self.name_token:
                search_start_token = self.name_token
            elif self.type:
                search_start_token = self.type_end_token

            interesting_token = tokenutil.Search(
                search_start_token,
                Type.FLAG_DESCRIPTION_TYPES | Type.FLAG_ENDING_TYPES)
            if interesting_token.type in Type.FLAG_DESCRIPTION_TYPES:
                self.description_start_token = interesting_token
                self.description_end_token, self.description = (
                    _GetEndTokenAndContents(interesting_token))
Exemplo n.º 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
    """
    if self.__ContainsRecordType(token):
      # We should bail out and not emit any warnings for this annotation.
      # TODO(nicksantos): Support record types for real.
      state.GetDocComment().Invalidate()
      return

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

    # Store some convenience variables
    first_in_line = token.IsFirstInLine()
    last_in_line = token.IsLastInLine()
    type = token.type

    if 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.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
        # without types.
        if (flag.flag_type not in ('suppress', 'enum') and
            (flag.type == None or flag.type == '' or flag.type.isspace())):
          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 type == Type.DOUBLE_QUOTE_STRING_START:
      next = token.next
      while next.type == Type.STRING_TEXT:
        if javascripttokenizer.JavaScriptTokenizer.SINGLE_QUOTE.search(
            next.string):
          break
        next = next.next
      else:
        self._HandleError(
            errors.UNNECESSARY_DOUBLE_QUOTED_STRING,
            'Single-quoted string preferred over double-quoted string.',
            token,
            Position.All(token.string))

    elif type == Type.END_DOC_COMMENT:
      if (FLAGS.strict and not self._is_html and state.InTopLevel() and
          not state.InBlock()):

        # Check if we're in a fileoverview or constructor JsDoc.
        doc_comment = state.GetDocComment()
        is_constructor = (doc_comment.HasFlag('constructor') or
            doc_comment.HasFlag('interface'))
        is_file_overview = doc_comment.HasFlag('fileoverview')

        # 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.next
        if (not next or
            (not is_file_overview and next.type in Type.NON_CODE_TYPES)):
          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_overview:
          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

        if is_file_overview and blank_lines == 0:
          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_overview 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.AtBeginning(),
              expected_blank_lines - blank_lines)

    elif 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.AtBeginning())
          elif (not function.has_return and function.doc and
                function.doc.HasFlag('return') and
                not state.InInterfaceMethod()):
            return_flag = function.doc.GetFlag('return')
            if (return_flag.type is None or (
                'undefined' not in return_flag.type and
                'void' not in return_flag.type and
                '*' not in return_flag.type)):
              self._HandleError(
                  errors.UNNECESSARY_RETURN_DOCUMENTATION,
                  'Found @return JsDoc on function that returns nothing',
                  return_flag.flag_token, Position.AtBeginning())

      if state.InFunction() and state.IsFunctionClose():
        is_immediately_called = (token.next and
                                 token.next.type == Type.START_PAREN)
        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):
          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.AtBeginning())

    elif 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.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 type == Type.OPERATOR:
      # 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 token.next.type 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.AtEnd(token.string))
    elif type == Type.WHITESPACE:
      # 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.All(token.string))
Exemplo n.º 5
0
    def __init__(self, flag_token):
        """Creates the DocFlag object and attaches it to the given start token.

    Args:
      flag_token: The starting token of the flag.
    """
        self.flag_token = flag_token
        self.flag_type = flag_token.string.strip().lstrip('@')

        # Extract type, if applicable.
        self.type = None
        self.type_start_token = None
        self.type_end_token = None
        if self.flag_type in self.HAS_TYPE:
            brace = tokenutil.SearchUntil(flag_token, [Type.DOC_START_BRACE],
                                          Type.FLAG_ENDING_TYPES)
            if brace:
                end_token, contents = _GetMatchingEndBraceAndContents(brace)
                self.type = contents
                self.type_start_token = brace
                self.type_end_token = end_token
            elif (self.flag_type in self.TYPE_ONLY
                  and flag_token.next.type not in Type.FLAG_ENDING_TYPES):
                self.type_start_token = flag_token.next
                self.type_end_token, self.type = _GetEndTokenAndContents(
                    self.type_start_token)
                if self.type is not None:
                    self.type = self.type.strip()

        # Extract name, if applicable.
        self.name_token = None
        self.name = None
        if self.flag_type in self.HAS_NAME:
            # Handle bad case, name could be immediately after flag token.
            self.name_token = _GetNextIdentifierToken(flag_token)

            # Handle good case, if found token is after type start, look for
            # identifier after type end, since types contain identifiers.
            if (self.type and self.name_token and tokenutil.Compare(
                    self.name_token, self.type_start_token) > 0):
                self.name_token = _GetNextIdentifierToken(self.type_end_token)

            if self.name_token:
                self.name = self.name_token.string

        # Extract description, if applicable.
        self.description_start_token = None
        self.description_end_token = None
        self.description = None
        if self.flag_type in self.HAS_DESCRIPTION:
            search_start_token = flag_token
            if self.name_token and self.type_end_token:
                if tokenutil.Compare(self.type_end_token, self.name_token) > 0:
                    search_start_token = self.type_end_token
                else:
                    search_start_token = self.name_token
            elif self.name_token:
                search_start_token = self.name_token
            elif self.type:
                search_start_token = self.type_end_token

            interesting_token = tokenutil.Search(
                search_start_token,
                Type.FLAG_DESCRIPTION_TYPES | Type.FLAG_ENDING_TYPES)
            if interesting_token.type in Type.FLAG_DESCRIPTION_TYPES:
                self.description_start_token = interesting_token
                self.description_end_token, self.description = (
                    _GetEndTokenAndContents(interesting_token))
Exemplo n.º 6
0
 def _CompareContexts(context1, context2):
   """Sorts contexts 1 and 2 by start token document position."""
   return tokenutil.Compare(context1.start_token, context2.start_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
    """
        if self.__ContainsRecordType(token):
            # We should bail out and not emit any warnings for this annotation.
            # TODO(nicksantos): Support record types for real.
            state.GetDocComment().Invalidate()
            return

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

        # Store some convenience variables
        namespaces_info = self._namespaces_info

        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 (error_check.ShouldCheck(Rule.OPTIONAL_TYPE_MARKER)
                        and flag.type is not None and flag.name is not None):
                    # Check for optional marker in type.
                    if (flag.type.endswith('=')
                            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.type.endswith('=')
                          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
                # without types.
                if (flag.flag_type not in ('suppress', 'enum')
                        and (not flag.type or flag.type.isspace())):
                    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.SINGLE_QUOTE_STRING_START:
            next_token = token.next
            while next_token.type == Type.STRING_TEXT:
                if javascripttokenizer.JavaScriptTokenizer.DOUBLE_QUOTE.search(
                        next_token.string):
                    break
                next_token = next_token.next
            else:
                self._HandleError(
                    errors.UNNECESSARY_SINGLE_QUOTED_STRING,
                    'Double-quoted string preferred over single-quoted string.',
                    token, 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.InBlock()):

                # Check if we're in a fileoverview or constructor JsDoc.
                is_constructor = (doc_comment.HasFlag('constructor')
                                  or doc_comment.HasFlag('interface'))
                is_file_overview = doc_comment.HasFlag('fileoverview')

                # 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_overview
                            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_overview:
                    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

                if is_file_overview and blank_lines == 0:
                    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_overview 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.AtBeginning(),
                                      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.AtBeginning())
                    elif (not function.has_return and not function.has_throw
                          and function.doc and function.doc.HasFlag('return')
                          and not state.InInterfaceMethod()):
                        return_flag = function.doc.GetFlag('return')
                        if (return_flag.type is None
                                or ('undefined' not in return_flag.type
                                    and 'void' not in return_flag.type
                                    and '*' not in return_flag.type)):
                            self._HandleError(
                                errors.UNNECESSARY_RETURN_DOCUMENTATION,
                                'Found @return JsDoc on function that returns nothing',
                                return_flag.flag_token, Position.AtBeginning())

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

        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 token.next.type
                    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.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.All(token.string))