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. |