コード例 #1
0
    def _ProcessToken(self):
        """Process the given token."""
        token = self._token
        token.metadata = self._CreateMetaData()
        context = (self._ProcessContext() or self._context)
        token.metadata.context = context
        token.metadata.last_code = self._last_code

        # Determine the operator type of the token, if applicable.
        if token.type == TokenType.OPERATOR:
            token.metadata.operator_type = self._GetOperatorType(token)

        # Determine if there is an implied semicolon after the token.
        if token.type != TokenType.SEMICOLON:
            next_code = tokenutil.SearchExcept(token, TokenType.NON_CODE_TYPES)
            # A statement like if (x) does not need a semicolon after it
            is_implied_block = self._context == EcmaContext.IMPLIED_BLOCK
            is_last_code_in_line = token.IsCode() and (
                not next_code or next_code.line_number != token.line_number)
            is_continued_identifier = (token.type == TokenType.IDENTIFIER
                                       and token.string.endswith('.'))
            is_continued_operator = (token.type == TokenType.OPERATOR and
                                     not token.metadata.IsUnaryPostOperator())
            is_continued_dot = token.string == '.'
            next_code_is_operator = next_code and next_code.type == TokenType.OPERATOR
            next_code_is_dot = next_code and next_code.string == '.'
            is_end_of_block = (
                token.type == TokenType.END_BLOCK
                and token.metadata.context.type != EcmaContext.OBJECT_LITERAL)
            is_multiline_string = token.type == TokenType.STRING_TEXT
            is_continued_var_decl = (
                token.IsKeyword('var') and next_code
                and (next_code.type
                     in [TokenType.IDENTIFIER, TokenType.SIMPLE_LVALUE])
                and token.line_number < next_code.line_number)
            next_code_is_block = next_code and next_code.type == TokenType.START_BLOCK
            if (is_last_code_in_line and self._StatementCouldEndInContext()
                    and not is_multiline_string and not is_end_of_block
                    and not is_continued_var_decl
                    and not is_continued_identifier
                    and not is_continued_operator and not is_continued_dot
                    and not next_code_is_dot and not next_code_is_operator
                    and not is_implied_block and not next_code_is_block):
                token.metadata.is_implied_semicolon = True
                self._EndStatement()
コード例 #2
0
    def GetBlockType(self, token):
        """Determine the block type given a START_BLOCK token.

        Code blocks come after parameters, keywords  like else, and closing parens.

        Args:
          token: The current token. Can be assumed to be type START_BLOCK
        Returns:
          Code block type for current token.
        """
        last_code = tokenutil.SearchExcept(token,
                                           Type.NON_CODE_TYPES,
                                           reverse=True)
        if last_code.type in (Type.END_PARAMETERS, Type.END_PAREN, Type.KEYWORD
                              ) and not last_code.IsKeyword('return'):
            return self.CODE
        else:
            return self.OBJECT_LITERAL
コード例 #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:
            # This check is for object literal end block tokens, but there is no need
            # to test that condition since a comma at the end of any other kind of
            # block is undoubtedly a parse error.
            last_code = token.metadata.last_code
            if last_code.IsOperator(','):
                self._HandleError(
                    errors.COMMA_AT_END_OF_LITERAL,
                    'Illegal comma at end of object literal', last_code,
                    position=Position.All(last_code.string))

            if state.InFunction() and state.IsFunctionClose():
                is_immediately_called = (token.next and
                                         token.next.type == Type.START_PAREN)
                if state.InTopLevelFunction():
                    # 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.
                if state.InAssignedFunction():
                    if not is_immediately_called and (
                            last_in_line or token.next.type != Type.SEMICOLON):
                        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:
            if token.previous and token.previous.type == Type.KEYWORD:
                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
                if (
                        before_space and before_space.line_number == token.line_number and
                        before_space.type == Type.IDENTIFIER):
                    self._HandleError(
                        errors.EXTRA_SPACE, 'Extra space before "("',
                        token.previous,
                        position=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.
            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))

            if token.type == Type.END_BRACKET:
                last_code = token.metadata.last_code
                if last_code.IsOperator(','):
                    self._HandleError(
                        errors.COMMA_AT_END_OF_LITERAL,
                        'Illegal comma at end of array literal', last_code,
                        position=Position.All(last_code.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 re.split(r'\||,', flag.type):
                        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.flag_type in state.GetDocFlag().HAS_TYPE:
                if flag.type_start_token is not None:
                    self._CheckForMissingSpaceBeforeToken(
                        token.attached_object.type_start_token)

                if flag.type and not flag.type.isspace():
                    self._CheckJsDocType(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_.
                    for f in ('desc', 'hidden', 'meaning'):
                        if (jsdoc.HasFlag(f)
                            and not identifier.startswith('MSG_')
                            and identifier.find('.MSG_') == -1):
                            self._HandleError(
                                errors.INVALID_USE_OF_DESC_TAG,
                                'Member "%s" should not have @%s JsDoc' % (
                                identifier, f),
                                token)

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

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

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

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

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

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

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

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

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

        # This check is also orthogonal since it is based on metadata.
        if token.metadata.is_implied_semicolon:
            self._HandleError(errors.MISSING_SEMICOLON,
                              'Missing semicolon at end of line', token)
コード例 #4
0
    def _ProcessContext(self):
        """Process the context at the current token.

        Returns:
          The context that should be assigned to the current token, or None if
          the current context after this method should be used.

        Raises:
          ParseError: When the token appears in an invalid context.
        """
        token = self._token
        token_type = token.type

        if self._context.type in EcmaContext.BLOCK_TYPES:
            # Whenever we're in a block, we add a statement context.  We make an
            # exception for switch statements since they can only contain case: and
            # default: and therefore don't directly contain statements.
            # The block we add here may be immediately removed in some cases, but
            # that causes no harm.
            parent = self._context.parent
            if not parent or parent.type != EcmaContext.SWITCH:
                self._AddContext(EcmaContext.STATEMENT)

        elif self._context.type == EcmaContext.ARRAY_LITERAL:
            self._AddContext(EcmaContext.LITERAL_ELEMENT)

        if token_type == TokenType.START_PAREN:
            if self._last_code and self._last_code.IsKeyword('for'):
                # for loops contain multiple statements in the group unlike while,
                # switch, if, etc.
                self._AddContext(EcmaContext.FOR_GROUP_BLOCK)
            else:
                self._AddContext(EcmaContext.GROUP)

        elif token_type == TokenType.END_PAREN:
            result = self._PopContextType(EcmaContext.GROUP,
                                          EcmaContext.FOR_GROUP_BLOCK)
            keyword_token = result.start_token.metadata.last_code
            # keyword_token will not exist if the open paren is the first line of the
            # file, for example if all code is wrapped in an immediately executed
            # annonymous function.
            if keyword_token and keyword_token.string in ('if', 'for',
                                                          'while'):
                next_code = tokenutil.SearchExcept(token,
                                                   TokenType.NON_CODE_TYPES)
                if next_code.type != TokenType.START_BLOCK:
                    # Check for do-while.
                    is_do_while = False
                    pre_keyword_token = keyword_token.metadata.last_code
                    if (pre_keyword_token
                            and pre_keyword_token.type == TokenType.END_BLOCK):
                        start_block_token = pre_keyword_token.metadata.context.start_token
                        is_do_while = start_block_token.metadata.last_code.string == 'do'

                    # If it's not do-while, it's an implied block.
                    if not is_do_while:
                        self._AddContext(EcmaContext.IMPLIED_BLOCK)
                        token.metadata.is_implied_block = True

            return result

        # else (not else if) with no open brace after it should be considered the
        # start of an implied block, similar to the case with if, for, and while
        # above.
        elif (token_type == TokenType.KEYWORD and token.string == 'else'):
            next_code = tokenutil.SearchExcept(token, TokenType.NON_CODE_TYPES)
            if (next_code.type != TokenType.START_BLOCK
                    and (next_code.type != TokenType.KEYWORD
                         or next_code.string != 'if')):
                self._AddContext(EcmaContext.IMPLIED_BLOCK)
                token.metadata.is_implied_block = True

        elif token_type == TokenType.START_PARAMETERS:
            self._AddContext(EcmaContext.PARAMETERS)

        elif token_type == TokenType.END_PARAMETERS:
            return self._PopContextType(EcmaContext.PARAMETERS)

        elif token_type == TokenType.START_BRACKET:
            if (self._last_code and self._last_code.type
                    in TokenType.EXPRESSION_ENDER_TYPES):
                self._AddContext(EcmaContext.INDEX)
            else:
                self._AddContext(EcmaContext.ARRAY_LITERAL)

        elif token_type == TokenType.END_BRACKET:
            return self._PopContextType(EcmaContext.INDEX,
                                        EcmaContext.ARRAY_LITERAL)

        elif token_type == TokenType.START_BLOCK:
            if (self._last_code.type in (TokenType.END_PAREN,
                                         TokenType.END_PARAMETERS)
                    or self._last_code.IsKeyword('else')
                    or self._last_code.IsKeyword('do')
                    or self._last_code.IsKeyword('try')
                    or self._last_code.IsKeyword('finally')
                    or (self._last_code.IsOperator(':')
                        and self._last_code.metadata.context.type
                        == EcmaContext.CASE_BLOCK)):
                # else, do, try, and finally all might have no () before {.
                # Also, handle the bizzare syntax case 10: {...}.
                self._AddContext(EcmaContext.BLOCK)
            else:
                self._AddContext(EcmaContext.OBJECT_LITERAL)

        elif token_type == TokenType.END_BLOCK:
            context = self._PopContextType(EcmaContext.BLOCK,
                                           EcmaContext.OBJECT_LITERAL)
            if self._context.type == EcmaContext.SWITCH:
                # The end of the block also means the end of the switch statement it
                # applies to.
                return self._PopContext()
            return context

        elif token.IsKeyword('switch'):
            self._AddContext(EcmaContext.SWITCH)

        elif (token_type == TokenType.KEYWORD
              and token.string in ('case', 'default')
              and self._context.type != EcmaContext.OBJECT_LITERAL):
            # Pop up to but not including the switch block.
            while self._context.parent.type != EcmaContext.SWITCH:
                self._PopContext()
                if self._context.parent is None:
                    raise ParseError(
                        token, 'Encountered case/default statement '
                        'without switch statement')

        elif token.IsOperator('?'):
            self._AddContext(EcmaContext.TERNARY_TRUE)

        elif token.IsOperator(':'):
            if self._context.type == EcmaContext.OBJECT_LITERAL:
                self._AddContext(EcmaContext.LITERAL_ELEMENT)

            elif self._context.type == EcmaContext.TERNARY_TRUE:
                self._PopContext()
                self._AddContext(EcmaContext.TERNARY_FALSE)

            # Handle nested ternary statements like:
            # foo = bar ? baz ? 1 : 2 : 3
            # When we encounter the second ":" the context is
            # ternary_false > ternary_true > statement > root
            elif (self._context.type == EcmaContext.TERNARY_FALSE
                  and self._context.parent.type == EcmaContext.TERNARY_TRUE):
                self._PopContext()  # Leave current ternary false context.
                self._PopContext()  # Leave current parent ternary true
                self._AddContext(EcmaContext.TERNARY_FALSE)

            elif self._context.parent.type == EcmaContext.SWITCH:
                self._AddContext(EcmaContext.CASE_BLOCK)

        elif token.IsKeyword('var'):
            self._AddContext(EcmaContext.VAR)

        elif token.IsOperator(','):
            while self._context.type not in (EcmaContext.VAR,
                                             EcmaContext.ARRAY_LITERAL,
                                             EcmaContext.OBJECT_LITERAL,
                                             EcmaContext.STATEMENT,
                                             EcmaContext.PARAMETERS,
                                             EcmaContext.GROUP):
                self._PopContext()

        elif token_type == TokenType.SEMICOLON:
            self._EndStatement()
コード例 #5
0
    def HandleToken(self, token, last_non_space_token):
        """Handles the given token and updates state.

        Args:
          token: The token to handle.
          last_non_space_token:
        """
        self._is_block_close = False

        if not self._first_token:
            self._first_token = token

        # Track block depth.
        type = token.type
        if type == Type.START_BLOCK:
            self._block_depth += 1

            # Subclasses need to handle block start very differently because
            # whether a block is a CODE or OBJECT_LITERAL block varies significantly
            # by language.
            self._block_types.append(self.GetBlockType(token))

            # When entering a function body, record its parameters.
            if self.InFunction():
                function = self._function_stack[-1]
                if self._block_depth == function.block_depth + 1:
                    function.parameters = self.GetParams()

        # Track block depth.
        elif type == Type.END_BLOCK:
            self._is_block_close = not self.InObjectLiteral()
            self._block_depth -= 1
            self._block_types.pop()

        # Track parentheses depth.
        elif type == Type.START_PAREN:
            self._paren_depth += 1

        # Track parentheses depth.
        elif type == Type.END_PAREN:
            self._paren_depth -= 1

        elif type == Type.COMMENT:
            self._last_comment = token.string

        elif type == Type.START_DOC_COMMENT:
            self._last_comment = None
            self._doc_comment = DocComment(token)

        elif type == Type.END_DOC_COMMENT:
            self._doc_comment.end_token = token

        elif type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG):
            flag = self._doc_flag(token)
            token.attached_object = flag
            self._doc_comment.AddFlag(flag)

            if flag.flag_type == 'suppress':
                self._doc_comment.AddSuppression(token)

        elif type == Type.FUNCTION_DECLARATION:
            last_code = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES,
                                               None, True)
            doc = None
            # Only functions outside of parens are eligible for documentation.
            if not self._paren_depth:
                doc = self._doc_comment

            name = ''
            is_assigned = last_code and (
                last_code.IsOperator('=') or last_code.IsOperator('||')
                or last_code.IsOperator('&&') or
                (last_code.IsOperator(':') and not self.InObjectLiteral()))
            if is_assigned:
                # TODO(robbyw): This breaks for x[2] = ...
                # Must use loop to find full function name in the case of line-wrapped
                # declarations (bug 1220601) like:
                # my.function.foo.
                #   bar = function() ...
                identifier = tokenutil.Search(last_code, Type.SIMPLE_LVALUE,
                                              None, True)
                while identifier and identifier.type in (Type.IDENTIFIER,
                                                         Type.SIMPLE_LVALUE):
                    name = identifier.string + name
                    # Traverse behind us, skipping whitespace and comments.
                    while True:
                        identifier = identifier.previous
                        if not identifier or not identifier.type in Type.NON_CODE_TYPES:
                            break

            else:
                next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
                while next_token and next_token.IsType(Type.FUNCTION_NAME):
                    name += next_token.string
                    next_token = tokenutil.Search(next_token,
                                                  Type.FUNCTION_NAME, 2)

            function = Function(self._block_depth, is_assigned, doc, name)
            function.start_token = token

            self._function_stack.append(function)
            self._functions_by_name[name] = function

            # Add a delimiter in stack for scope variables to define start of
            # function. This helps in popping variables of this function when
            # function declaration ends.
            self._variables_in_scope.append('')

        elif type == Type.START_PARAMETERS:
            self._cumulative_params = ''

        elif type == Type.PARAMETERS:
            self._cumulative_params += token.string
            self._variables_in_scope.extend(self.GetParams())

        elif type == Type.KEYWORD and token.string == 'return':
            next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
            if not next_token.IsType(Type.SEMICOLON):
                function = self.GetFunction()
                if function:
                    function.has_return = True

        elif type == Type.KEYWORD and token.string == 'throw':
            function = self.GetFunction()
            if function:
                function.has_throw = True

        elif type == Type.KEYWORD and token.string == 'var':
            function = self.GetFunction()
            next_token = tokenutil.Search(
                token, [Type.IDENTIFIER, Type.SIMPLE_LVALUE])

            if next_token:
                if next_token.type == Type.SIMPLE_LVALUE:
                    self._variables_in_scope.append(
                        next_token.values['identifier'])
                else:
                    self._variables_in_scope.append(next_token.string)

        elif type == Type.SIMPLE_LVALUE:
            identifier = token.values['identifier']
            jsdoc = self.GetDocComment()
            if jsdoc:
                self._documented_identifiers.add(identifier)

            self._HandleIdentifier(identifier, True)

        elif type == Type.IDENTIFIER:
            self._HandleIdentifier(token.string, False)

            # Detect documented non-assignments.
            next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
            if next_token and next_token.IsType(Type.SEMICOLON):
                if (self._last_non_space_token
                        and self._last_non_space_token.IsType(
                            Type.END_DOC_COMMENT)):
                    self._documented_identifiers.add(token.string)
コード例 #6
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
        """

        # For @param don't ignore record type.
        if (self.__ContainsRecordType(token)
                and 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'
                         or doc_comment.GetFlag('suppress').type
                         == 'unusedPrivateMembers'))
                    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.type.startswith('...')
                                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.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
                # and const without types.
                if (flag.flag_type not in ('suppress', 'enum', 'const')
                        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=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()):
                        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=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 = 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.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 = 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 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))
コード例 #7
0
    def CheckToken(self, token, state):
        """Checks a token for indentation errors.

        Args:
          token: The current token under consideration
          state: Additional information about the current tree state

        Returns:
          An error array [error code, error string, error token] if the token is
          improperly indented, or None if indentation is correct.
        """

        token_type = token.type
        indentation_errors = []
        stack = self._stack
        is_first = self._IsFirstNonWhitespaceTokenInLine(token)

        # Add tokens that could decrease indentation before checking.
        if token_type == Type.END_PAREN:
            self._PopTo(Type.START_PAREN)

        elif token_type == Type.END_PARAMETERS:
            self._PopTo(Type.START_PARAMETERS)

        elif token_type == Type.END_BRACKET:
            self._PopTo(Type.START_BRACKET)

        elif token_type == Type.END_BLOCK:
            start_token = self._PopTo(Type.START_BLOCK)
            # Check for required goog.scope comment.
            if start_token:
                goog_scope = tokenutil.GoogScopeOrNoneFromStartBlock(
                    start_token.token)
                if goog_scope is not None:
                    if not token.line.endswith(';  // goog.scope\n'):
                        if (token.line.find('//') > -1
                                and token.line.find('goog.scope') >
                                token.line.find('//')):
                            indentation_errors.append([
                                errors.MALFORMED_END_OF_SCOPE_COMMENT,
                                ('Malformed end of goog.scope comment. Please use the '
                                 'exact following syntax to close the scope:\n'
                                 '});  // goog.scope'), token,
                                Position(token.start_index, token.length)
                            ])
                        else:
                            indentation_errors.append([
                                errors.MISSING_END_OF_SCOPE_COMMENT,
                                ('Missing comment for end of goog.scope which opened at line '
                                 '%d. End the scope with:\n'
                                 '});  // goog.scope' %
                                 (start_token.line_number)), token,
                                Position(token.start_index, token.length)
                            ])

        elif token_type == Type.KEYWORD and token.string in ('case',
                                                             'default'):
            self._Add(self._PopTo(Type.START_BLOCK))

        elif is_first and token.string == '.':
            # This token should have been on the previous line, so treat it as if it
            # was there.
            info = TokenInfo(token)
            info.line_number = token.line_number - 1
            self._Add(info)

        elif token_type == Type.SEMICOLON:
            self._PopTransient()

        not_binary_operator = (token_type != Type.OPERATOR
                               or token.metadata.IsUnaryOperator())
        not_dot = token.string != '.'
        if is_first and not_binary_operator and not_dot and token.type not in (
                Type.COMMENT, Type.DOC_PREFIX, Type.STRING_TEXT):
            if flags.FLAGS.debug_indentation:
                print 'Line #%d: stack %r' % (token.line_number, stack)

            # Ignore lines that start in JsDoc since we don't check them properly yet.
            # TODO(robbyw): Support checking JsDoc indentation.
            # Ignore lines that start as multi-line strings since indentation is N/A.
            # Ignore lines that start with operators since we report that already.
            # Ignore lines with tabs since we report that already.
            expected = self._GetAllowableIndentations()
            actual = self._GetActualIndentation(token)

            # Special case comments describing else, case, and default.  Allow them
            # to outdent to the parent block.
            if token_type in Type.COMMENT_TYPES:
                next_code = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
                if next_code and next_code.type == Type.END_BLOCK:
                    next_code = tokenutil.SearchExcept(next_code,
                                                       Type.NON_CODE_TYPES)
                if next_code and next_code.string in ('else', 'case',
                                                      'default'):
                    # TODO(robbyw): This almost certainly introduces false negatives.
                    expected |= self._AddToEach(expected, -2)

            if actual >= 0 and actual not in expected:
                expected = sorted(expected)
                indentation_errors.append([
                    errors.WRONG_INDENTATION,
                    'Wrong indentation: expected any of {%s} but got %d' %
                    (', '.join(['%d' % x for x in expected]), actual), token,
                    Position(actual, expected[0])
                ])
                self._start_index_offset[
                    token.line_number] = expected[0] - actual

        # Add tokens that could increase indentation.
        if token_type == Type.START_BRACKET:
            self._Add(
                TokenInfo(token=token,
                          is_block=token.metadata.context.type ==
                          Context.ARRAY_LITERAL))

        elif token_type == Type.START_BLOCK or token.metadata.is_implied_block:
            self._Add(TokenInfo(token=token, is_block=True))

        elif token_type in (Type.START_PAREN, Type.START_PARAMETERS):
            self._Add(TokenInfo(token=token, is_block=False))

        elif token_type == Type.KEYWORD and token.string == 'return':
            self._Add(TokenInfo(token))

        elif not token.IsLastInLine() and (token.IsAssignment()
                                           or token.IsOperator('?')):
            self._Add(TokenInfo(token=token))

        # Handle implied block closes.
        if token.metadata.is_implied_block_close:
            self._PopToImpliedBlock()

        # Add some tokens only if they appear at the end of the line.
        is_last = self._IsLastCodeInLine(token)
        if is_last:
            if token_type == Type.OPERATOR:
                if token.string == ':':
                    if stack and stack[-1].token.string == '?':
                        # When a ternary : is on a different line than its '?', it doesn't
                        # add indentation.
                        if token.line_number == stack[-1].token.line_number:
                            self._Add(TokenInfo(token))
                    elif token.metadata.context.type == Context.CASE_BLOCK:
                        # Pop transient tokens from say, line continuations, e.g.,
                        # case x.
                        #     y:
                        # Want to pop the transient 4 space continuation indent.
                        self._PopTransient()
                        # Starting the body of the case statement, which is a type of
                        # block.
                        self._Add(TokenInfo(token=token, is_block=True))
                    elif token.metadata.context.type == Context.LITERAL_ELEMENT:
                        # When in an object literal, acts as operator indicating line
                        # continuations.
                        self._Add(TokenInfo(token))
                        pass
                    else:
                        # ':' might also be a statement label, no effect on indentation in
                        # this case.
                        pass

                elif token.string != ',':
                    self._Add(TokenInfo(token))
                else:
                    # The token is a comma.
                    if token.metadata.context.type == Context.VAR:
                        self._Add(TokenInfo(token))
                    elif token.metadata.context.type != Context.PARAMETERS:
                        self._PopTransient()

            elif (token.string.endswith('.')
                  and token_type in (Type.IDENTIFIER, Type.NORMAL)):
                self._Add(TokenInfo(token))
            elif token_type == Type.PARAMETERS and token.string.endswith(','):
                # Parameter lists.
                self._Add(TokenInfo(token))
            elif token.IsKeyword('var'):
                self._Add(TokenInfo(token))
            elif token.metadata.is_implied_semicolon:
                self._PopTransient()
        elif token.IsAssignment():
            self._Add(TokenInfo(token))

        return indentation_errors