def fixRequiresTest_withTestOnly(self, position):
        """Regression-tests sorting even with a goog.setTestOnly statement.

        Args:
          position: The position in the list where to insert the goog.setTestOnly
                    statement. Will be used to test all possible combinations for
                    this test.
        """
        input_lines = [
            'goog.provide(\'package.subpackage.Whatever\');', '',
            'goog.require(\'package.subpackage.ClassB\');',
            'goog.require(\'package.subpackage.ClassA\');'
        ]
        expected_lines = [
            'goog.provide(\'package.subpackage.Whatever\');', '',
            'goog.require(\'package.subpackage.ClassA\');',
            'goog.require(\'package.subpackage.ClassB\');'
        ]
        input_lines.insert(position, 'goog.setTestOnly();')
        expected_lines.insert(position, 'goog.setTestOnly();')

        token = testutil.TokenizeSourceAndRunEcmaPass(input_lines)

        sorter = requireprovidesorter.RequireProvideSorter()
        sorter.FixRequires(token)

        self.assertEquals(expected_lines, self._GetLines(token))
    def _CheckSortedRequiresProvides(self, token):
        """Checks that all goog.require and goog.provide statements are sorted.

        Note that this method needs to be run after missing statements are added to
        preserve alphabetical order.

        Args:
          token: The first token in the token stream.
        """
        sorter = requireprovidesorter.RequireProvideSorter()
        first_provide_token = sorter.CheckProvides(token)
        if first_provide_token:
            new_order = sorter.GetFixedProvideString(first_provide_token)
            self._HandleError(
                errors.GOOG_PROVIDES_NOT_ALPHABETIZED,
                'goog.provide classes must be alphabetized.  The correct code is:\n'
                + new_order,
                first_provide_token,
                position=Position.AtBeginning(),
                fix_data=first_provide_token)

        first_require_token = sorter.CheckRequires(token)
        if first_require_token:
            new_order = sorter.GetFixedRequireString(first_require_token)
            self._HandleError(
                errors.GOOG_REQUIRES_NOT_ALPHABETIZED,
                'goog.require classes must be alphabetized.  The correct code is:\n'
                + new_order,
                first_require_token,
                position=Position.AtBeginning(),
                fix_data=first_require_token)
    def testFixRequires_removeBlankLines(self):
        """Tests that blank lines are omitted in sorted goog.require statements."""
        input_lines = [
            'goog.provide(\'package.subpackage.Whatever\');', '',
            'goog.require(\'package.subpackage.ClassB\');', '',
            'goog.require(\'package.subpackage.ClassA\');'
        ]
        expected_lines = [
            'goog.provide(\'package.subpackage.Whatever\');', '',
            'goog.require(\'package.subpackage.ClassA\');',
            'goog.require(\'package.subpackage.ClassB\');'
        ]
        token = testutil.TokenizeSourceAndRunEcmaPass(input_lines)

        sorter = requireprovidesorter.RequireProvideSorter()
        sorter.FixRequires(token)

        self.assertEquals(expected_lines, self._GetLines(token))
    def testGetFixedRequireString(self):
        """Tests that fixed string constains proper comments also."""
        input_lines = [
            'goog.require(\'package.xyz\');',
            '/** This is needed for scope. **/',
            'goog.require(\'package.abcd\');'
        ]

        expected_lines = [
            '/** This is needed for scope. **/',
            'goog.require(\'package.abcd\');', 'goog.require(\'package.xyz\');'
        ]

        token = testutil.TokenizeSourceAndRunEcmaPass(input_lines)

        sorter = requireprovidesorter.RequireProvideSorter()
        fixed_require_string = sorter.GetFixedRequireString(token)

        self.assertEquals(expected_lines, fixed_require_string.splitlines())
    def HandleError(self, error):
        """Attempts to fix the error.

        Args:
          error: The error object
        """
        code = error.code
        token = error.token

        if code == errors.JSDOC_PREFER_QUESTION_TO_PIPE_NULL:
            iterator = token.attached_object.type_start_token
            if iterator.type == Type.DOC_START_BRACE or iterator.string.isspace(
            ):
                iterator = iterator.next

            leading_space = len(iterator.string) - len(
                iterator.string.lstrip())
            iterator.string = '%s?%s' % (' ' * leading_space,
                                         iterator.string.lstrip())

            # Cover the no outer brace case where the end token is part of the type.
            while iterator and iterator != token.attached_object.type_end_token.next:
                iterator.string = iterator.string.replace('null|', '').replace(
                    '|null', '')
                iterator = iterator.next

            # Create a new flag object with updated type info.
            token.attached_object = javascriptstatetracker.JsDocFlag(token)
            self._AddFix(token)

        elif code == errors.JSDOC_MISSING_OPTIONAL_TYPE:
            iterator = token.attached_object.type_end_token
            if iterator.type == Type.DOC_END_BRACE or iterator.string.isspace(
            ):
                iterator = iterator.previous

            ending_space = len(iterator.string) - len(iterator.string.rstrip())
            iterator.string = '%s=%s' % (iterator.string.rstrip(),
                                         ' ' * ending_space)

            # Create a new flag object with updated type info.
            token.attached_object = javascriptstatetracker.JsDocFlag(token)
            self._AddFix(token)

        elif code == errors.JSDOC_MISSING_VAR_ARGS_TYPE:
            iterator = token.attached_object.type_start_token
            if iterator.type == Type.DOC_START_BRACE or iterator.string.isspace(
            ):
                iterator = iterator.next

            starting_space = len(iterator.string) - len(
                iterator.string.lstrip())
            iterator.string = '%s...%s' % (' ' * starting_space,
                                           iterator.string.lstrip())

            # Create a new flag object with updated type info.
            token.attached_object = javascriptstatetracker.JsDocFlag(token)
            self._AddFix(token)

        elif code in (errors.MISSING_SEMICOLON_AFTER_FUNCTION,
                      errors.MISSING_SEMICOLON):
            semicolon_token = Token(';', Type.SEMICOLON, token.line,
                                    token.line_number)
            tokenutil.InsertTokenAfter(semicolon_token, token)
            token.metadata.is_implied_semicolon = False
            semicolon_token.metadata.is_implied_semicolon = False
            self._AddFix(token)

        elif code in (errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION,
                      errors.REDUNDANT_SEMICOLON,
                      errors.COMMA_AT_END_OF_LITERAL):
            self._DeleteToken(token)
            self._AddFix(token)

        elif code == errors.INVALID_JSDOC_TAG:
            if token.string == '@returns':
                token.string = '@return'
                self._AddFix(token)

        elif code == errors.FILE_MISSING_NEWLINE:
            # This error is fixed implicitly by the way we restore the file
            self._AddFix(token)

        elif code == errors.MISSING_SPACE:
            if error.fix_data:
                token.string = error.fix_data
                self._AddFix(token)
            elif error.position:
                if error.position.IsAtBeginning():
                    tokenutil.InsertSpaceTokenAfter(token.previous)
                elif error.position.IsAtEnd(token.string):
                    tokenutil.InsertSpaceTokenAfter(token)
                else:
                    token.string = error.position.Set(token.string, ' ')
                self._AddFix(token)

        elif code == errors.EXTRA_SPACE:
            if error.position:
                token.string = error.position.Set(token.string, '')
                self._AddFix(token)

        elif code == errors.MISSING_LINE:
            if error.position.IsAtBeginning():
                tokenutil.InsertBlankLineAfter(token.previous)
            else:
                tokenutil.InsertBlankLineAfter(token)
            self._AddFix(token)

        elif code == errors.EXTRA_LINE:
            self._DeleteToken(token)
            self._AddFix(token)

        elif code == errors.WRONG_BLANK_LINE_COUNT:
            if not token.previous:
                # TODO(user): Add an insertBefore method to tokenutil.
                return

            num_lines = error.fix_data
            should_delete = False

            if num_lines < 0:
                num_lines *= -1
                should_delete = True

            for unused_i in xrange(1, num_lines + 1):
                if should_delete:
                    # TODO(user): DeleteToken should update line numbers.
                    self._DeleteToken(token.previous)
                else:
                    tokenutil.InsertBlankLineAfter(token.previous)
                self._AddFix(token)

        elif code == errors.UNNECESSARY_DOUBLE_QUOTED_STRING:
            end_quote = tokenutil.Search(token, Type.DOUBLE_QUOTE_STRING_END)
            if end_quote:
                single_quote_start = Token("'", Type.SINGLE_QUOTE_STRING_START,
                                           token.line, token.line_number)
                single_quote_end = Token("'", Type.SINGLE_QUOTE_STRING_START,
                                         end_quote.line, token.line_number)

                tokenutil.InsertTokenAfter(single_quote_start, token)
                tokenutil.InsertTokenAfter(single_quote_end, end_quote)
                self._DeleteToken(token)
                self._DeleteToken(end_quote)
                self._AddFix([token, end_quote])

        elif code == errors.MISSING_BRACES_AROUND_TYPE:
            fixed_tokens = []
            start_token = token.attached_object.type_start_token

            if start_token.type != Type.DOC_START_BRACE:
                leading_space = (len(start_token.string) -
                                 len(start_token.string.lstrip()))
                if leading_space:
                    start_token = tokenutil.SplitToken(start_token,
                                                       leading_space)
                    # Fix case where start and end token were the same.
                    if token.attached_object.type_end_token == start_token.previous:
                        token.attached_object.type_end_token = start_token

                new_token = Token('{', Type.DOC_START_BRACE, start_token.line,
                                  start_token.line_number)
                tokenutil.InsertTokenAfter(new_token, start_token.previous)
                token.attached_object.type_start_token = new_token
                fixed_tokens.append(new_token)

            end_token = token.attached_object.type_end_token
            if end_token.type != Type.DOC_END_BRACE:
                # If the start token was a brace, the end token will be a
                # FLAG_ENDING_TYPE token, if there wasn't a starting brace then
                # the end token is the last token of the actual type.
                last_type = end_token
                if not fixed_tokens:
                    last_type = end_token.previous

                while last_type.string.isspace():
                    last_type = last_type.previous

                # If there was no starting brace then a lone end brace wouldn't have
                # been type end token. Now that we've added any missing start brace,
                # see if the last effective type token was an end brace.
                if last_type.type != Type.DOC_END_BRACE:
                    trailing_space = (len(last_type.string) -
                                      len(last_type.string.rstrip()))
                    if trailing_space:
                        tokenutil.SplitToken(
                            last_type,
                            len(last_type.string) - trailing_space)

                    new_token = Token('}', Type.DOC_END_BRACE, last_type.line,
                                      last_type.line_number)
                    tokenutil.InsertTokenAfter(new_token, last_type)
                    token.attached_object.type_end_token = new_token
                    fixed_tokens.append(new_token)

            self._AddFix(fixed_tokens)

        elif code == errors.GOOG_REQUIRES_NOT_ALPHABETIZED:
            require_start_token = error.fix_data
            sorter = requireprovidesorter.RequireProvideSorter()
            sorter.FixRequires(require_start_token)

            self._AddFix(require_start_token)

        elif code == errors.GOOG_PROVIDES_NOT_ALPHABETIZED:
            provide_start_token = error.fix_data
            sorter = requireprovidesorter.RequireProvideSorter()
            sorter.FixProvides(provide_start_token)

            self._AddFix(provide_start_token)

        elif code == errors.UNNECESSARY_BRACES_AROUND_INHERIT_DOC:
            if token.previous.string == '{' and token.next.string == '}':
                self._DeleteToken(token.previous)
                self._DeleteToken(token.next)
                self._AddFix([token])

        elif code == errors.INVALID_AUTHOR_TAG_DESCRIPTION:
            match = INVERTED_AUTHOR_SPEC.match(token.string)
            if match:
                token.string = '%s%s%s(%s)%s' % (
                    match.group('leading_whitespace'), match.group('email'),
                    match.group('whitespace_after_name'), match.group('name'),
                    match.group('trailing_characters'))
                self._AddFix(token)

        elif (code == errors.WRONG_INDENTATION
              and not FLAGS.disable_indentation_fixing):
            token = tokenutil.GetFirstTokenInSameLine(token)
            actual = error.position.start
            expected = error.position.length

            # Cases where first token is param but with leading spaces.
            if (len(token.string.lstrip()) == len(token.string) - actual
                    and token.string.lstrip()):
                token.string = token.string.lstrip()
                actual = 0

            if token.type in (Type.WHITESPACE,
                              Type.PARAMETERS) and actual != 0:
                token.string = token.string.lstrip() + (' ' * expected)
                self._AddFix([token])
            else:
                # We need to add indentation.
                new_token = Token(' ' * expected, Type.WHITESPACE, token.line,
                                  token.line_number)
                # Note that we'll never need to add indentation at the first line,
                # since it will always not be indented.  Therefore it's safe to assume
                # token.previous exists.
                tokenutil.InsertTokenAfter(new_token, token.previous)
                self._AddFix([token])

        elif code in [
                errors.MALFORMED_END_OF_SCOPE_COMMENT,
                errors.MISSING_END_OF_SCOPE_COMMENT
        ]:
            # Only fix cases where }); is found with no trailing content on the line
            # other than a comment. Value of 'token' is set to } for this error.
            if (token.type == Type.END_BLOCK
                    and token.next.type == Type.END_PAREN
                    and token.next.next.type == Type.SEMICOLON):
                current_token = token.next.next.next
                removed_tokens = []
                while current_token and current_token.line_number == token.line_number:
                    if current_token.IsAnyType(Type.WHITESPACE,
                                               Type.START_SINGLE_LINE_COMMENT,
                                               Type.COMMENT):
                        removed_tokens.append(current_token)
                        current_token = current_token.next
                    else:
                        return

                if removed_tokens:
                    self._DeleteTokens(removed_tokens[0], len(removed_tokens))

                whitespace_token = Token('  ', Type.WHITESPACE, token.line,
                                         token.line_number)
                start_comment_token = Token('//',
                                            Type.START_SINGLE_LINE_COMMENT,
                                            token.line, token.line_number)
                comment_token = Token(' goog.scope', Type.COMMENT, token.line,
                                      token.line_number)
                insertion_tokens = [
                    whitespace_token, start_comment_token, comment_token
                ]

                tokenutil.InsertTokensAfter(insertion_tokens, token.next.next)
                self._AddFix(removed_tokens + insertion_tokens)

        elif code in [errors.EXTRA_GOOG_PROVIDE, errors.EXTRA_GOOG_REQUIRE]:
            tokens_in_line = tokenutil.GetAllTokensInSameLine(token)
            self._DeleteTokens(tokens_in_line[0], len(tokens_in_line))
            self._AddFix(tokens_in_line)

        elif code in [
                errors.MISSING_GOOG_PROVIDE, errors.MISSING_GOOG_REQUIRE
        ]:
            is_provide = code == errors.MISSING_GOOG_PROVIDE
            is_require = code == errors.MISSING_GOOG_REQUIRE

            missing_namespaces = error.fix_data[0]
            need_blank_line = error.fix_data[1]

            if need_blank_line is None:
                # TODO(user): This happens when there are no existing
                # goog.provide or goog.require statements to position new statements
                # relative to. Consider handling this case with a heuristic.
                return

            insert_location = token.previous

            # If inserting a missing require with no existing requires, insert a
            # blank line first.
            if need_blank_line and is_require:
                tokenutil.InsertBlankLineAfter(insert_location)
                insert_location = insert_location.next

            for missing_namespace in missing_namespaces:
                new_tokens = self._GetNewRequireOrProvideTokens(
                    is_provide, missing_namespace,
                    insert_location.line_number + 1)
                tokenutil.InsertLineAfter(insert_location, new_tokens)
                insert_location = new_tokens[-1]
                self._AddFix(new_tokens)

            # If inserting a missing provide with no existing provides, insert a
            # blank line after.
            if need_blank_line and is_provide:
                tokenutil.InsertBlankLineAfter(insert_location)