aboutsummaryrefslogtreecommitdiff
path: root/tools/closure_linter-2.3.4/closure_linter/error_fixer.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/closure_linter-2.3.4/closure_linter/error_fixer.py')
-rwxr-xr-xtools/closure_linter-2.3.4/closure_linter/error_fixer.py414
1 files changed, 414 insertions, 0 deletions
diff --git a/tools/closure_linter-2.3.4/closure_linter/error_fixer.py b/tools/closure_linter-2.3.4/closure_linter/error_fixer.py
new file mode 100755
index 0000000..684a503
--- /dev/null
+++ b/tools/closure_linter-2.3.4/closure_linter/error_fixer.py
@@ -0,0 +1,414 @@
+#!/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."""
+
+__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*)'
+ '(?P<name>[^(]+)'
+ '(?P<whitespace_after_name>\s+)'
+ '\('
+ '(?P<email>[^\s]+@[^)\s]+)'
+ '\)'
+ '(?P<trailing_characters>.*)')
+
+FLAGS = flags.FLAGS
+flags.DEFINE_boolean('disable_indentation_fixing', False,
+ 'Whether to disable automatic fixing of indentation.')
+
+
+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
+
+ 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_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 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 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):
+ tokenutil.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.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.JSDOC_TAG_DESCRIPTION_ENDS_WITH_INVALID_CHARACTER:
+ 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:
+ tokenutil.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 i in xrange(1, num_lines + 1):
+ if should_delete:
+ # TODO(user): DeleteToken should update line numbers.
+ tokenutil.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)
+ tokenutil.DeleteToken(token)
+ tokenutil.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 == '}':
+ tokenutil.DeleteToken(token.previous)
+ tokenutil.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
+
+ 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.EXTRA_GOOG_PROVIDE, errors.EXTRA_GOOG_REQUIRE]:
+ tokens_in_line = tokenutil.GetAllTokensInSameLine(token)
+ tokenutil.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)
+
+ 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 FinishFile(self):
+ """Called when the current file has finished style checking.
+
+ Used to go back and fix any errors in the file.
+ """
+ if self._file_fix_count:
+ f = self._external_file
+ if not f:
+ print 'Fixed %d errors in %s' % (self._file_fix_count, self._file_name)
+ f = open(self._file_name, 'w')
+
+ token = self._file_token
+ char_count = 0
+ while token:
+ f.write(token.string)
+ char_count += len(token.string)
+
+ if token.IsLastInLine():
+ f.write('\n')
+ 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()