diff options
Diffstat (limited to 'tools/closure_linter/build/lib/closure_linter/error_fixer.py')
-rw-r--r-- | tools/closure_linter/build/lib/closure_linter/error_fixer.py | 618 |
1 files changed, 0 insertions, 618 deletions
diff --git a/tools/closure_linter/build/lib/closure_linter/error_fixer.py b/tools/closure_linter/build/lib/closure_linter/error_fixer.py deleted file mode 100644 index 88f9c720ab..0000000000 --- a/tools/closure_linter/build/lib/closure_linter/error_fixer.py +++ /dev/null @@ -1,618 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2007 The Closure Linter Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS-IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Main class responsible for automatically fixing simple style violations.""" - -# Allow non-Google copyright -# pylint: disable=g-bad-file-header - -__author__ = 'robbyw@google.com (Robert Walker)' - -import re - -import gflags as flags -from closure_linter import errors -from closure_linter import javascriptstatetracker -from closure_linter import javascripttokens -from closure_linter import requireprovidesorter -from closure_linter import tokenutil -from closure_linter.common import errorhandler - -# Shorthand -Token = javascripttokens.JavaScriptToken -Type = javascripttokens.JavaScriptTokenType - -END_OF_FLAG_TYPE = re.compile(r'(}?\s*)$') - -# Regex to represent common mistake inverting author name and email as -# @author User Name (user@company) -INVERTED_AUTHOR_SPEC = re.compile(r'(?P<leading_whitespace>\s*)' - r'(?P<name>[^(]+)' - r'(?P<whitespace_after_name>\s+)' - r'\(' - r'(?P<email>[^\s]+@[^)\s]+)' - r'\)' - r'(?P<trailing_characters>.*)') - -FLAGS = flags.FLAGS -flags.DEFINE_boolean('disable_indentation_fixing', False, - 'Whether to disable automatic fixing of indentation.') -flags.DEFINE_list('fix_error_codes', [], 'A list of specific error codes to ' - 'fix. Defaults to all supported error codes when empty. ' - 'See errors.py for a list of error codes.') - - -class ErrorFixer(errorhandler.ErrorHandler): - """Object that fixes simple style errors.""" - - def __init__(self, external_file=None): - """Initialize the error fixer. - - Args: - external_file: If included, all output will be directed to this file - instead of overwriting the files the errors are found in. - """ - errorhandler.ErrorHandler.__init__(self) - - self._file_name = None - self._file_token = None - self._external_file = external_file - - try: - self._fix_error_codes = set([errors.ByName(error.upper()) for error in - FLAGS.fix_error_codes]) - except KeyError as ke: - raise ValueError('Unknown error code ' + ke.args[0]) - - def HandleFile(self, filename, first_token): - """Notifies this ErrorPrinter that subsequent errors are in filename. - - Args: - filename: The name of the file about to be checked. - first_token: The first token in the file. - """ - self._file_name = filename - self._file_is_html = filename.endswith('.html') or filename.endswith('.htm') - self._file_token = first_token - self._file_fix_count = 0 - self._file_changed_lines = set() - - def _AddFix(self, tokens): - """Adds the fix to the internal count. - - Args: - tokens: The token or sequence of tokens changed to fix an error. - """ - self._file_fix_count += 1 - if hasattr(tokens, 'line_number'): - self._file_changed_lines.add(tokens.line_number) - else: - for token in tokens: - self._file_changed_lines.add(token.line_number) - - def _FixJsDocPipeNull(self, js_type): - """Change number|null or null|number to ?number. - - Args: - js_type: The typeannotation.TypeAnnotation instance to fix. - """ - - # Recurse into all sub_types if the error was at a deeper level. - map(self._FixJsDocPipeNull, js_type.IterTypes()) - - if js_type.type_group and len(js_type.sub_types) == 2: - # Find and remove the null sub_type: - sub_type = None - for sub_type in js_type.sub_types: - if sub_type.identifier == 'null': - map(tokenutil.DeleteToken, sub_type.tokens) - self._AddFix(sub_type.tokens) - break - else: - return - - first_token = js_type.FirstToken() - question_mark = Token('?', Type.DOC_TYPE_MODIFIER, first_token.line, - first_token.line_number) - tokenutil.InsertTokenBefore(question_mark, first_token) - js_type.tokens.insert(0, question_mark) - js_type.tokens.remove(sub_type) - js_type.or_null = True - - # Now also remove the separator, which is in the parent's token list, - # either before or after the sub_type, there is exactly one. Scan for it. - for token in js_type.tokens: - if (token and isinstance(token, Token) and - token.type == Type.DOC_TYPE_MODIFIER and token.string == '|'): - tokenutil.DeleteToken(token) - self._AddFix(token) - break - - def HandleError(self, error): - """Attempts to fix the error. - - Args: - error: The error object - """ - code = error.code - token = error.token - - if self._fix_error_codes and code not in self._fix_error_codes: - return - - if code == errors.JSDOC_PREFER_QUESTION_TO_PIPE_NULL: - self._FixJsDocPipeNull(token.attached_object.jstype) - - 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.LINE_STARTS_WITH_OPERATOR: - # Remove whitespace following the operator so the line starts clean. - self._StripSpace(token, before=False) - - # Remove the operator. - tokenutil.DeleteToken(token) - self._AddFix(token) - - insertion_point = tokenutil.GetPreviousCodeToken(token) - - # Insert a space between the previous token and the new operator. - space = Token(' ', Type.WHITESPACE, insertion_point.line, - insertion_point.line_number) - tokenutil.InsertTokenAfter(space, insertion_point) - - # Insert the operator on the end of the previous line. - new_token = Token(token.string, token.type, insertion_point.line, - insertion_point.line_number) - tokenutil.InsertTokenAfter(new_token, space) - self._AddFix(new_token) - - elif code == errors.LINE_ENDS_WITH_DOT: - # Remove whitespace preceding the operator to remove trailing whitespace. - self._StripSpace(token, before=True) - - # Remove the dot. - tokenutil.DeleteToken(token) - self._AddFix(token) - - insertion_point = tokenutil.GetNextCodeToken(token) - - # Insert the dot at the beginning of the next line of code. - new_token = Token(token.string, token.type, insertion_point.line, - insertion_point.line_number) - tokenutil.InsertTokenBefore(new_token, insertion_point) - self._AddFix(new_token) - - 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) - num_delete_tokens = len(tokens_in_line) - # If line being deleted is preceded and succeed with blank lines then - # delete one blank line also. - if (tokens_in_line[0].previous and tokens_in_line[-1].next - and tokens_in_line[0].previous.type == Type.BLANK_LINE - and tokens_in_line[-1].next.type == Type.BLANK_LINE): - num_delete_tokens += 1 - self._DeleteTokens(tokens_in_line[0], num_delete_tokens) - self._AddFix(tokens_in_line) - - elif code in [errors.MISSING_GOOG_PROVIDE, errors.MISSING_GOOG_REQUIRE]: - missing_namespaces = error.fix_data[0] - need_blank_line = error.fix_data[1] or (not token.previous) - - insert_location = Token('', Type.NORMAL, '', token.line_number - 1) - dummy_first_token = insert_location - tokenutil.InsertTokenBefore(insert_location, token) - - # If inserting a blank line check blank line does not exist before - # token to avoid extra blank lines. - if (need_blank_line and insert_location.previous - and insert_location.previous.type != Type.BLANK_LINE): - tokenutil.InsertBlankLineAfter(insert_location) - insert_location = insert_location.next - - for missing_namespace in missing_namespaces: - new_tokens = self._GetNewRequireOrProvideTokens( - code == errors.MISSING_GOOG_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 blank line check blank line does not exist after - # token to avoid extra blank lines. - if (need_blank_line and insert_location.next - and insert_location.next.type != Type.BLANK_LINE): - tokenutil.InsertBlankLineAfter(insert_location) - - tokenutil.DeleteToken(dummy_first_token) - - def _StripSpace(self, token, before): - """Strip whitespace tokens either preceding or following the given token. - - Args: - token: The token. - before: If true, strip space before the token, if false, after it. - """ - token = token.previous if before else token.next - while token and token.type == Type.WHITESPACE: - tokenutil.DeleteToken(token) - token = token.previous if before else token.next - - def _GetNewRequireOrProvideTokens(self, is_provide, namespace, line_number): - """Returns a list of tokens to create a goog.require/provide statement. - - Args: - is_provide: True if getting tokens for a provide, False for require. - namespace: The required or provided namespaces to get tokens for. - line_number: The line number the new require or provide statement will be - on. - - Returns: - Tokens to create a new goog.require or goog.provide statement. - """ - string = 'goog.require' - if is_provide: - string = 'goog.provide' - line_text = string + '(\'' + namespace + '\');\n' - return [ - Token(string, Type.IDENTIFIER, line_text, line_number), - Token('(', Type.START_PAREN, line_text, line_number), - Token('\'', Type.SINGLE_QUOTE_STRING_START, line_text, line_number), - Token(namespace, Type.STRING_TEXT, line_text, line_number), - Token('\'', Type.SINGLE_QUOTE_STRING_END, line_text, line_number), - Token(')', Type.END_PAREN, line_text, line_number), - Token(';', Type.SEMICOLON, line_text, line_number) - ] - - def _DeleteToken(self, token): - """Deletes the specified token from the linked list of tokens. - - Updates instance variables pointing to tokens such as _file_token if - they reference the deleted token. - - Args: - token: The token to delete. - """ - if token == self._file_token: - self._file_token = token.next - - tokenutil.DeleteToken(token) - - def _DeleteTokens(self, token, token_count): - """Deletes the given number of tokens starting with the given token. - - Updates instance variables pointing to tokens such as _file_token if - they reference the deleted token. - - Args: - token: The first token to delete. - token_count: The total number of tokens to delete. - """ - if token == self._file_token: - for unused_i in xrange(token_count): - self._file_token = self._file_token.next - - tokenutil.DeleteTokens(token, token_count) - - def FinishFile(self): - """Called when the current file has finished style checking. - - Used to go back and fix any errors in the file. It currently supports both - js and html files. For js files it does a simple dump of all tokens, but in - order to support html file, we need to merge the original file with the new - token set back together. This works because the tokenized html file is the - original html file with all non js lines kept but blanked out with one blank - line token per line of html. - """ - if self._file_fix_count: - # Get the original file content for html. - if self._file_is_html: - f = open(self._file_name, 'r') - original_lines = f.readlines() - f.close() - - f = self._external_file - if not f: - error_noun = 'error' if self._file_fix_count == 1 else 'errors' - print 'Fixed %d %s in %s' % ( - self._file_fix_count, error_noun, self._file_name) - f = open(self._file_name, 'w') - - token = self._file_token - # Finding the first not deleted token. - while token.is_deleted: - token = token.next - # If something got inserted before first token (e.g. due to sorting) - # then move to start. Bug 8398202. - while token.previous: - token = token.previous - char_count = 0 - line = '' - while token: - line += token.string - char_count += len(token.string) - - if token.IsLastInLine(): - # We distinguish if a blank line in html was from stripped original - # file or newly added error fix by looking at the "org_line_number" - # field on the token. It is only set in the tokenizer, so for all - # error fixes, the value should be None. - if (line or not self._file_is_html or - token.orig_line_number is None): - f.write(line) - f.write('\n') - else: - f.write(original_lines[token.orig_line_number - 1]) - line = '' - if char_count > 80 and token.line_number in self._file_changed_lines: - print 'WARNING: Line %d of %s is now longer than 80 characters.' % ( - token.line_number, self._file_name) - - char_count = 0 - - token = token.next - - if not self._external_file: - # Close the file if we created it - f.close() |