| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # | 2 # |
| 3 # Copyright 2007 The Closure Linter Authors. All Rights Reserved. | 3 # Copyright 2007 The Closure Linter Authors. All Rights Reserved. |
| 4 # | 4 # |
| 5 # Licensed under the Apache License, Version 2.0 (the "License"); | 5 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 # you may not use this file except in compliance with the License. | 6 # you may not use this file except in compliance with the License. |
| 7 # You may obtain a copy of the License at | 7 # You may obtain a copy of the License at |
| 8 # | 8 # |
| 9 # http://www.apache.org/licenses/LICENSE-2.0 | 9 # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 # | 10 # |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 43 r'(?P<name>[^(]+)' | 43 r'(?P<name>[^(]+)' |
| 44 r'(?P<whitespace_after_name>\s+)' | 44 r'(?P<whitespace_after_name>\s+)' |
| 45 r'\(' | 45 r'\(' |
| 46 r'(?P<email>[^\s]+@[^)\s]+)' | 46 r'(?P<email>[^\s]+@[^)\s]+)' |
| 47 r'\)' | 47 r'\)' |
| 48 r'(?P<trailing_characters>.*)') | 48 r'(?P<trailing_characters>.*)') |
| 49 | 49 |
| 50 FLAGS = flags.FLAGS | 50 FLAGS = flags.FLAGS |
| 51 flags.DEFINE_boolean('disable_indentation_fixing', False, | 51 flags.DEFINE_boolean('disable_indentation_fixing', False, |
| 52 'Whether to disable automatic fixing of indentation.') | 52 'Whether to disable automatic fixing of indentation.') |
| 53 flags.DEFINE_list('fix_error_codes', [], 'A list of specific error codes to ' |
| 54 'fix. Defaults to all supported error codes when empty. ' |
| 55 'See errors.py for a list of error codes.') |
| 53 | 56 |
| 54 | 57 |
| 55 class ErrorFixer(errorhandler.ErrorHandler): | 58 class ErrorFixer(errorhandler.ErrorHandler): |
| 56 """Object that fixes simple style errors.""" | 59 """Object that fixes simple style errors.""" |
| 57 | 60 |
| 58 def __init__(self, external_file=None): | 61 def __init__(self, external_file=None): |
| 59 """Initialize the error fixer. | 62 """Initialize the error fixer. |
| 60 | 63 |
| 61 Args: | 64 Args: |
| 62 external_file: If included, all output will be directed to this file | 65 external_file: If included, all output will be directed to this file |
| 63 instead of overwriting the files the errors are found in. | 66 instead of overwriting the files the errors are found in. |
| 64 """ | 67 """ |
| 65 errorhandler.ErrorHandler.__init__(self) | 68 errorhandler.ErrorHandler.__init__(self) |
| 66 | 69 |
| 67 self._file_name = None | 70 self._file_name = None |
| 68 self._file_token = None | 71 self._file_token = None |
| 69 self._external_file = external_file | 72 self._external_file = external_file |
| 70 | 73 |
| 74 try: |
| 75 self._fix_error_codes = set([errors.ByName(error.upper()) for error in |
| 76 FLAGS.fix_error_codes]) |
| 77 except KeyError as ke: |
| 78 raise ValueError('Unknown error code ' + ke.args[0]) |
| 79 |
| 71 def HandleFile(self, filename, first_token): | 80 def HandleFile(self, filename, first_token): |
| 72 """Notifies this ErrorPrinter that subsequent errors are in filename. | 81 """Notifies this ErrorPrinter that subsequent errors are in filename. |
| 73 | 82 |
| 74 Args: | 83 Args: |
| 75 filename: The name of the file about to be checked. | 84 filename: The name of the file about to be checked. |
| 76 first_token: The first token in the file. | 85 first_token: The first token in the file. |
| 77 """ | 86 """ |
| 78 self._file_name = filename | 87 self._file_name = filename |
| 79 self._file_is_html = filename.endswith('.html') or filename.endswith('.htm') | 88 self._file_is_html = filename.endswith('.html') or filename.endswith('.htm') |
| 80 self._file_token = first_token | 89 self._file_token = first_token |
| 81 self._file_fix_count = 0 | 90 self._file_fix_count = 0 |
| 82 self._file_changed_lines = set() | 91 self._file_changed_lines = set() |
| 83 | 92 |
| 84 def _AddFix(self, tokens): | 93 def _AddFix(self, tokens): |
| 85 """Adds the fix to the internal count. | 94 """Adds the fix to the internal count. |
| 86 | 95 |
| 87 Args: | 96 Args: |
| 88 tokens: The token or sequence of tokens changed to fix an error. | 97 tokens: The token or sequence of tokens changed to fix an error. |
| 89 """ | 98 """ |
| 90 self._file_fix_count += 1 | 99 self._file_fix_count += 1 |
| 91 if hasattr(tokens, 'line_number'): | 100 if hasattr(tokens, 'line_number'): |
| 92 self._file_changed_lines.add(tokens.line_number) | 101 self._file_changed_lines.add(tokens.line_number) |
| 93 else: | 102 else: |
| 94 for token in tokens: | 103 for token in tokens: |
| 95 self._file_changed_lines.add(token.line_number) | 104 self._file_changed_lines.add(token.line_number) |
| 96 | 105 |
| 106 def _FixJsDocPipeNull(self, js_type): |
| 107 """Change number|null or null|number to ?number. |
| 108 |
| 109 Args: |
| 110 js_type: The typeannotation.TypeAnnotation instance to fix. |
| 111 """ |
| 112 |
| 113 # Recurse into all sub_types if the error was at a deeper level. |
| 114 map(self._FixJsDocPipeNull, js_type.IterTypes()) |
| 115 |
| 116 if js_type.type_group and len(js_type.sub_types) == 2: |
| 117 # Find and remove the null sub_type: |
| 118 sub_type = None |
| 119 for sub_type in js_type.sub_types: |
| 120 if sub_type.identifier == 'null': |
| 121 map(tokenutil.DeleteToken, sub_type.tokens) |
| 122 self._AddFix(sub_type.tokens) |
| 123 break |
| 124 else: |
| 125 return |
| 126 |
| 127 first_token = js_type.FirstToken() |
| 128 question_mark = Token('?', Type.DOC_TYPE_MODIFIER, first_token.line, |
| 129 first_token.line_number) |
| 130 tokenutil.InsertTokenBefore(question_mark, first_token) |
| 131 js_type.tokens.insert(0, question_mark) |
| 132 js_type.tokens.remove(sub_type) |
| 133 js_type.sub_types.remove(sub_type) |
| 134 js_type.or_null = True |
| 135 |
| 136 # Now also remove the separator, which is in the parent's token list, |
| 137 # either before or after the sub_type, there is exactly one. Scan for it. |
| 138 for token in js_type.tokens: |
| 139 if (token and isinstance(token, Token) and |
| 140 token.type == Type.DOC_TYPE_MODIFIER and token.string == '|'): |
| 141 tokenutil.DeleteToken(token) |
| 142 js_type.tokens.remove(token) |
| 143 self._AddFix(token) |
| 144 break |
| 145 |
| 97 def HandleError(self, error): | 146 def HandleError(self, error): |
| 98 """Attempts to fix the error. | 147 """Attempts to fix the error. |
| 99 | 148 |
| 100 Args: | 149 Args: |
| 101 error: The error object | 150 error: The error object |
| 102 """ | 151 """ |
| 103 code = error.code | 152 code = error.code |
| 104 token = error.token | 153 token = error.token |
| 105 | 154 |
| 155 if self._fix_error_codes and code not in self._fix_error_codes: |
| 156 return |
| 157 |
| 106 if code == errors.JSDOC_PREFER_QUESTION_TO_PIPE_NULL: | 158 if code == errors.JSDOC_PREFER_QUESTION_TO_PIPE_NULL: |
| 107 iterator = token.attached_object.type_start_token | 159 self._FixJsDocPipeNull(token.attached_object.jstype) |
| 108 if iterator.type == Type.DOC_START_BRACE or iterator.string.isspace(): | |
| 109 iterator = iterator.next | |
| 110 | |
| 111 leading_space = len(iterator.string) - len(iterator.string.lstrip()) | |
| 112 iterator.string = '%s?%s' % (' ' * leading_space, | |
| 113 iterator.string.lstrip()) | |
| 114 | |
| 115 # Cover the no outer brace case where the end token is part of the type. | |
| 116 while iterator and iterator != token.attached_object.type_end_token.next: | |
| 117 iterator.string = iterator.string.replace( | |
| 118 'null|', '').replace('|null', '') | |
| 119 iterator = iterator.next | |
| 120 | |
| 121 # Create a new flag object with updated type info. | |
| 122 token.attached_object = javascriptstatetracker.JsDocFlag(token) | |
| 123 self._AddFix(token) | |
| 124 | 160 |
| 125 elif code == errors.JSDOC_MISSING_OPTIONAL_TYPE: | 161 elif code == errors.JSDOC_MISSING_OPTIONAL_TYPE: |
| 126 iterator = token.attached_object.type_end_token | 162 iterator = token.attached_object.type_end_token |
| 127 if iterator.type == Type.DOC_END_BRACE or iterator.string.isspace(): | 163 if iterator.type == Type.DOC_END_BRACE or iterator.string.isspace(): |
| 128 iterator = iterator.previous | 164 iterator = iterator.previous |
| 129 | 165 |
| 130 ending_space = len(iterator.string) - len(iterator.string.rstrip()) | 166 ending_space = len(iterator.string) - len(iterator.string.rstrip()) |
| 131 iterator.string = '%s=%s' % (iterator.string.rstrip(), | 167 iterator.string = '%s=%s' % (iterator.string.rstrip(), |
| 132 ' ' * ending_space) | 168 ' ' * ending_space) |
| 133 | 169 |
| (...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 278 len(last_type.string) - trailing_space) | 314 len(last_type.string) - trailing_space) |
| 279 | 315 |
| 280 new_token = Token('}', Type.DOC_END_BRACE, last_type.line, | 316 new_token = Token('}', Type.DOC_END_BRACE, last_type.line, |
| 281 last_type.line_number) | 317 last_type.line_number) |
| 282 tokenutil.InsertTokenAfter(new_token, last_type) | 318 tokenutil.InsertTokenAfter(new_token, last_type) |
| 283 token.attached_object.type_end_token = new_token | 319 token.attached_object.type_end_token = new_token |
| 284 fixed_tokens.append(new_token) | 320 fixed_tokens.append(new_token) |
| 285 | 321 |
| 286 self._AddFix(fixed_tokens) | 322 self._AddFix(fixed_tokens) |
| 287 | 323 |
| 324 elif code == errors.LINE_STARTS_WITH_OPERATOR: |
| 325 # Remove whitespace following the operator so the line starts clean. |
| 326 self._StripSpace(token, before=False) |
| 327 |
| 328 # Remove the operator. |
| 329 tokenutil.DeleteToken(token) |
| 330 self._AddFix(token) |
| 331 |
| 332 insertion_point = tokenutil.GetPreviousCodeToken(token) |
| 333 |
| 334 # Insert a space between the previous token and the new operator. |
| 335 space = Token(' ', Type.WHITESPACE, insertion_point.line, |
| 336 insertion_point.line_number) |
| 337 tokenutil.InsertTokenAfter(space, insertion_point) |
| 338 |
| 339 # Insert the operator on the end of the previous line. |
| 340 new_token = Token(token.string, token.type, insertion_point.line, |
| 341 insertion_point.line_number) |
| 342 tokenutil.InsertTokenAfter(new_token, space) |
| 343 self._AddFix(new_token) |
| 344 |
| 345 elif code == errors.LINE_ENDS_WITH_DOT: |
| 346 # Remove whitespace preceding the operator to remove trailing whitespace. |
| 347 self._StripSpace(token, before=True) |
| 348 |
| 349 # Remove the dot. |
| 350 tokenutil.DeleteToken(token) |
| 351 self._AddFix(token) |
| 352 |
| 353 insertion_point = tokenutil.GetNextCodeToken(token) |
| 354 |
| 355 # Insert the dot at the beginning of the next line of code. |
| 356 new_token = Token(token.string, token.type, insertion_point.line, |
| 357 insertion_point.line_number) |
| 358 tokenutil.InsertTokenBefore(new_token, insertion_point) |
| 359 self._AddFix(new_token) |
| 360 |
| 288 elif code == errors.GOOG_REQUIRES_NOT_ALPHABETIZED: | 361 elif code == errors.GOOG_REQUIRES_NOT_ALPHABETIZED: |
| 289 require_start_token = error.fix_data | 362 require_start_token = error.fix_data |
| 290 sorter = requireprovidesorter.RequireProvideSorter() | 363 sorter = requireprovidesorter.RequireProvideSorter() |
| 291 sorter.FixRequires(require_start_token) | 364 sorter.FixRequires(require_start_token) |
| 292 | 365 |
| 293 self._AddFix(require_start_token) | 366 self._AddFix(require_start_token) |
| 294 | 367 |
| 295 elif code == errors.GOOG_PROVIDES_NOT_ALPHABETIZED: | 368 elif code == errors.GOOG_PROVIDES_NOT_ALPHABETIZED: |
| 296 provide_start_token = error.fix_data | 369 provide_start_token = error.fix_data |
| 297 sorter = requireprovidesorter.RequireProvideSorter() | 370 sorter = requireprovidesorter.RequireProvideSorter() |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 368 comment_token = Token(' goog.scope', Type.COMMENT, token.line, | 441 comment_token = Token(' goog.scope', Type.COMMENT, token.line, |
| 369 token.line_number) | 442 token.line_number) |
| 370 insertion_tokens = [whitespace_token, start_comment_token, | 443 insertion_tokens = [whitespace_token, start_comment_token, |
| 371 comment_token] | 444 comment_token] |
| 372 | 445 |
| 373 tokenutil.InsertTokensAfter(insertion_tokens, token.next.next) | 446 tokenutil.InsertTokensAfter(insertion_tokens, token.next.next) |
| 374 self._AddFix(removed_tokens + insertion_tokens) | 447 self._AddFix(removed_tokens + insertion_tokens) |
| 375 | 448 |
| 376 elif code in [errors.EXTRA_GOOG_PROVIDE, errors.EXTRA_GOOG_REQUIRE]: | 449 elif code in [errors.EXTRA_GOOG_PROVIDE, errors.EXTRA_GOOG_REQUIRE]: |
| 377 tokens_in_line = tokenutil.GetAllTokensInSameLine(token) | 450 tokens_in_line = tokenutil.GetAllTokensInSameLine(token) |
| 378 self._DeleteTokens(tokens_in_line[0], len(tokens_in_line)) | 451 num_delete_tokens = len(tokens_in_line) |
| 452 # If line being deleted is preceded and succeed with blank lines then |
| 453 # delete one blank line also. |
| 454 if (tokens_in_line[0].previous and tokens_in_line[-1].next |
| 455 and tokens_in_line[0].previous.type == Type.BLANK_LINE |
| 456 and tokens_in_line[-1].next.type == Type.BLANK_LINE): |
| 457 num_delete_tokens += 1 |
| 458 self._DeleteTokens(tokens_in_line[0], num_delete_tokens) |
| 379 self._AddFix(tokens_in_line) | 459 self._AddFix(tokens_in_line) |
| 380 | 460 |
| 381 elif code in [errors.MISSING_GOOG_PROVIDE, errors.MISSING_GOOG_REQUIRE]: | 461 elif code in [errors.MISSING_GOOG_PROVIDE, errors.MISSING_GOOG_REQUIRE]: |
| 382 is_provide = code == errors.MISSING_GOOG_PROVIDE | 462 missing_namespaces = error.fix_data[0] |
| 383 is_require = code == errors.MISSING_GOOG_REQUIRE | 463 need_blank_line = error.fix_data[1] or (not token.previous) |
| 384 | 464 |
| 385 missing_namespaces = error.fix_data[0] | 465 insert_location = Token('', Type.NORMAL, '', token.line_number - 1) |
| 386 need_blank_line = error.fix_data[1] | 466 dummy_first_token = insert_location |
| 467 tokenutil.InsertTokenBefore(insert_location, token) |
| 387 | 468 |
| 388 if need_blank_line is None: | 469 # If inserting a blank line check blank line does not exist before |
| 389 # TODO(user): This happens when there are no existing | 470 # token to avoid extra blank lines. |
| 390 # goog.provide or goog.require statements to position new statements | 471 if (need_blank_line and insert_location.previous |
| 391 # relative to. Consider handling this case with a heuristic. | 472 and insert_location.previous.type != Type.BLANK_LINE): |
| 392 return | |
| 393 | |
| 394 insert_location = token.previous | |
| 395 | |
| 396 # If inserting a missing require with no existing requires, insert a | |
| 397 # blank line first. | |
| 398 if need_blank_line and is_require: | |
| 399 tokenutil.InsertBlankLineAfter(insert_location) | 473 tokenutil.InsertBlankLineAfter(insert_location) |
| 400 insert_location = insert_location.next | 474 insert_location = insert_location.next |
| 401 | 475 |
| 402 for missing_namespace in missing_namespaces: | 476 for missing_namespace in missing_namespaces: |
| 403 new_tokens = self._GetNewRequireOrProvideTokens( | 477 new_tokens = self._GetNewRequireOrProvideTokens( |
| 404 is_provide, missing_namespace, insert_location.line_number + 1) | 478 code == errors.MISSING_GOOG_PROVIDE, |
| 479 missing_namespace, insert_location.line_number + 1) |
| 405 tokenutil.InsertLineAfter(insert_location, new_tokens) | 480 tokenutil.InsertLineAfter(insert_location, new_tokens) |
| 406 insert_location = new_tokens[-1] | 481 insert_location = new_tokens[-1] |
| 407 self._AddFix(new_tokens) | 482 self._AddFix(new_tokens) |
| 408 | 483 |
| 409 # If inserting a missing provide with no existing provides, insert a | 484 # If inserting a blank line check blank line does not exist after |
| 410 # blank line after. | 485 # token to avoid extra blank lines. |
| 411 if need_blank_line and is_provide: | 486 if (need_blank_line and insert_location.next |
| 487 and insert_location.next.type != Type.BLANK_LINE): |
| 412 tokenutil.InsertBlankLineAfter(insert_location) | 488 tokenutil.InsertBlankLineAfter(insert_location) |
| 413 | 489 |
| 490 tokenutil.DeleteToken(dummy_first_token) |
| 491 |
| 492 def _StripSpace(self, token, before): |
| 493 """Strip whitespace tokens either preceding or following the given token. |
| 494 |
| 495 Args: |
| 496 token: The token. |
| 497 before: If true, strip space before the token, if false, after it. |
| 498 """ |
| 499 token = token.previous if before else token.next |
| 500 while token and token.type == Type.WHITESPACE: |
| 501 tokenutil.DeleteToken(token) |
| 502 token = token.previous if before else token.next |
| 503 |
| 414 def _GetNewRequireOrProvideTokens(self, is_provide, namespace, line_number): | 504 def _GetNewRequireOrProvideTokens(self, is_provide, namespace, line_number): |
| 415 """Returns a list of tokens to create a goog.require/provide statement. | 505 """Returns a list of tokens to create a goog.require/provide statement. |
| 416 | 506 |
| 417 Args: | 507 Args: |
| 418 is_provide: True if getting tokens for a provide, False for require. | 508 is_provide: True if getting tokens for a provide, False for require. |
| 419 namespace: The required or provided namespaces to get tokens for. | 509 namespace: The required or provided namespaces to get tokens for. |
| 420 line_number: The line number the new require or provide statement will be | 510 line_number: The line number the new require or provide statement will be |
| 421 on. | 511 on. |
| 422 | 512 |
| 423 Returns: | 513 Returns: |
| (...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 521 print 'WARNING: Line %d of %s is now longer than 80 characters.' % ( | 611 print 'WARNING: Line %d of %s is now longer than 80 characters.' % ( |
| 522 token.line_number, self._file_name) | 612 token.line_number, self._file_name) |
| 523 | 613 |
| 524 char_count = 0 | 614 char_count = 0 |
| 525 | 615 |
| 526 token = token.next | 616 token = token.next |
| 527 | 617 |
| 528 if not self._external_file: | 618 if not self._external_file: |
| 529 # Close the file if we created it | 619 # Close the file if we created it |
| 530 f.close() | 620 f.close() |
| OLD | NEW |