| Index: third_party/closure_linter/closure_linter/error_fixer.py
|
| diff --git a/third_party/closure_linter/closure_linter/error_fixer.py b/third_party/closure_linter/closure_linter/error_fixer.py
|
| index b3d3162f1d963efe234f5c5e99b6b79c5a9f0367..221550a202441a41368e82083f732ee2ff99a97b 100755
|
| --- a/third_party/closure_linter/closure_linter/error_fixer.py
|
| +++ b/third_party/closure_linter/closure_linter/error_fixer.py
|
| @@ -50,6 +50,9 @@ INVERTED_AUTHOR_SPEC = re.compile(r'(?P<leading_whitespace>\s*)'
|
| 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):
|
| @@ -68,6 +71,12 @@ class ErrorFixer(errorhandler.ErrorHandler):
|
| 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.
|
|
|
| @@ -94,6 +103,46 @@ class ErrorFixer(errorhandler.ErrorHandler):
|
| 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.sub_types.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)
|
| + js_type.tokens.remove(token)
|
| + self._AddFix(token)
|
| + break
|
| +
|
| def HandleError(self, error):
|
| """Attempts to fix the error.
|
|
|
| @@ -103,24 +152,11 @@ class ErrorFixer(errorhandler.ErrorHandler):
|
| 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
|
| + if self._fix_error_codes and code not in self._fix_error_codes:
|
| + return
|
|
|
| - # Create a new flag object with updated type info.
|
| - token.attached_object = javascriptstatetracker.JsDocFlag(token)
|
| - self._AddFix(token)
|
| + 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
|
| @@ -285,6 +321,43 @@ class ErrorFixer(errorhandler.ErrorHandler):
|
|
|
| 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()
|
| @@ -375,42 +448,59 @@ class ErrorFixer(errorhandler.ErrorHandler):
|
|
|
| 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))
|
| + 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]:
|
| - 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]
|
| + need_blank_line = error.fix_data[1] or (not token.previous)
|
|
|
| - 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('', Type.NORMAL, '', token.line_number - 1)
|
| + dummy_first_token = insert_location
|
| + tokenutil.InsertTokenBefore(insert_location, token)
|
|
|
| - 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:
|
| + # 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(
|
| - is_provide, missing_namespace, insert_location.line_number + 1)
|
| + 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 missing provide with no existing provides, insert a
|
| - # blank line after.
|
| - if need_blank_line and is_provide:
|
| + # 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.
|
|
|
|
|