/external/chromium-trace/trace-viewer/third_party/closure_linter/closure_linter/error_fixer.py
Python | 447 lines | 322 code | 49 blank | 76 comment | 47 complexity | 1e9f817e1a9221bfc6b3e9c0146c0f21 MD5 | raw file
- #!/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.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:
- tokenutil.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)
- 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()