OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # | |
3 # Copyright 2007 The Closure Linter Authors. All Rights Reserved. | |
4 # | |
5 # Licensed under the Apache License, Version 2.0 (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 | |
8 # | |
9 # http://www.apache.org/licenses/LICENSE-2.0 | |
10 # | |
11 # Unless required by applicable law or agreed to in writing, software | |
12 # distributed under the License is distributed on an "AS-IS" BASIS, | |
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 # See the License for the specific language governing permissions and | |
15 # limitations under the License. | |
16 | |
17 """Main class responsible for automatically fixing simple style violations.""" | |
18 | |
19 # Allow non-Google copyright | |
20 # pylint: disable=g-bad-file-header | |
21 | |
22 __author__ = 'robbyw@google.com (Robert Walker)' | |
23 | |
24 import re | |
25 | |
26 import gflags as flags | |
27 from closure_linter import errors | |
28 from closure_linter import javascriptstatetracker | |
29 from closure_linter import javascripttokens | |
30 from closure_linter import requireprovidesorter | |
31 from closure_linter import tokenutil | |
32 from closure_linter.common import errorhandler | |
33 | |
34 # Shorthand | |
35 Token = javascripttokens.JavaScriptToken | |
36 Type = javascripttokens.JavaScriptTokenType | |
37 | |
38 END_OF_FLAG_TYPE = re.compile(r'(}?\s*)$') | |
39 | |
40 # Regex to represent common mistake inverting author name and email as | |
41 # @author User Name (user@company) | |
42 INVERTED_AUTHOR_SPEC = re.compile(r'(?P<leading_whitespace>\s*)' | |
43 r'(?P<name>[^(]+)' | |
44 r'(?P<whitespace_after_name>\s+)' | |
45 r'\(' | |
46 r'(?P<email>[^\s]+@[^)\s]+)' | |
47 r'\)' | |
48 r'(?P<trailing_characters>.*)') | |
49 | |
50 FLAGS = flags.FLAGS | |
51 flags.DEFINE_boolean('disable_indentation_fixing', False, | |
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.') | |
56 | |
57 | |
58 class ErrorFixer(errorhandler.ErrorHandler): | |
59 """Object that fixes simple style errors.""" | |
60 | |
61 def __init__(self, external_file=None): | |
62 """Initialize the error fixer. | |
63 | |
64 Args: | |
65 external_file: If included, all output will be directed to this file | |
66 instead of overwriting the files the errors are found in. | |
67 """ | |
68 errorhandler.ErrorHandler.__init__(self) | |
69 | |
70 self._file_name = None | |
71 self._file_token = None | |
72 self._external_file = external_file | |
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 | |
80 def HandleFile(self, filename, first_token): | |
81 """Notifies this ErrorPrinter that subsequent errors are in filename. | |
82 | |
83 Args: | |
84 filename: The name of the file about to be checked. | |
85 first_token: The first token in the file. | |
86 """ | |
87 self._file_name = filename | |
88 self._file_is_html = filename.endswith('.html') or filename.endswith('.htm') | |
89 self._file_token = first_token | |
90 self._file_fix_count = 0 | |
91 self._file_changed_lines = set() | |
92 | |
93 def _AddFix(self, tokens): | |
94 """Adds the fix to the internal count. | |
95 | |
96 Args: | |
97 tokens: The token or sequence of tokens changed to fix an error. | |
98 """ | |
99 self._file_fix_count += 1 | |
100 if hasattr(tokens, 'line_number'): | |
101 self._file_changed_lines.add(tokens.line_number) | |
102 else: | |
103 for token in tokens: | |
104 self._file_changed_lines.add(token.line_number) | |
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 | |
146 def HandleError(self, error): | |
147 """Attempts to fix the error. | |
148 | |
149 Args: | |
150 error: The error object | |
151 """ | |
152 code = error.code | |
153 token = error.token | |
154 | |
155 if self._fix_error_codes and code not in self._fix_error_codes: | |
156 return | |
157 | |
158 if code == errors.JSDOC_PREFER_QUESTION_TO_PIPE_NULL: | |
159 self._FixJsDocPipeNull(token.attached_object.jstype) | |
160 | |
161 elif code == errors.JSDOC_MISSING_OPTIONAL_TYPE: | |
162 iterator = token.attached_object.type_end_token | |
163 if iterator.type == Type.DOC_END_BRACE or iterator.string.isspace(): | |
164 iterator = iterator.previous | |
165 | |
166 ending_space = len(iterator.string) - len(iterator.string.rstrip()) | |
167 iterator.string = '%s=%s' % (iterator.string.rstrip(), | |
168 ' ' * ending_space) | |
169 | |
170 # Create a new flag object with updated type info. | |
171 token.attached_object = javascriptstatetracker.JsDocFlag(token) | |
172 self._AddFix(token) | |
173 | |
174 elif code == errors.JSDOC_MISSING_VAR_ARGS_TYPE: | |
175 iterator = token.attached_object.type_start_token | |
176 if iterator.type == Type.DOC_START_BRACE or iterator.string.isspace(): | |
177 iterator = iterator.next | |
178 | |
179 starting_space = len(iterator.string) - len(iterator.string.lstrip()) | |
180 iterator.string = '%s...%s' % (' ' * starting_space, | |
181 iterator.string.lstrip()) | |
182 | |
183 # Create a new flag object with updated type info. | |
184 token.attached_object = javascriptstatetracker.JsDocFlag(token) | |
185 self._AddFix(token) | |
186 | |
187 elif code in (errors.MISSING_SEMICOLON_AFTER_FUNCTION, | |
188 errors.MISSING_SEMICOLON): | |
189 semicolon_token = Token(';', Type.SEMICOLON, token.line, | |
190 token.line_number) | |
191 tokenutil.InsertTokenAfter(semicolon_token, token) | |
192 token.metadata.is_implied_semicolon = False | |
193 semicolon_token.metadata.is_implied_semicolon = False | |
194 self._AddFix(token) | |
195 | |
196 elif code in (errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION, | |
197 errors.REDUNDANT_SEMICOLON, | |
198 errors.COMMA_AT_END_OF_LITERAL): | |
199 self._DeleteToken(token) | |
200 self._AddFix(token) | |
201 | |
202 elif code == errors.INVALID_JSDOC_TAG: | |
203 if token.string == '@returns': | |
204 token.string = '@return' | |
205 self._AddFix(token) | |
206 | |
207 elif code == errors.FILE_MISSING_NEWLINE: | |
208 # This error is fixed implicitly by the way we restore the file | |
209 self._AddFix(token) | |
210 | |
211 elif code == errors.MISSING_SPACE: | |
212 if error.fix_data: | |
213 token.string = error.fix_data | |
214 self._AddFix(token) | |
215 elif error.position: | |
216 if error.position.IsAtBeginning(): | |
217 tokenutil.InsertSpaceTokenAfter(token.previous) | |
218 elif error.position.IsAtEnd(token.string): | |
219 tokenutil.InsertSpaceTokenAfter(token) | |
220 else: | |
221 token.string = error.position.Set(token.string, ' ') | |
222 self._AddFix(token) | |
223 | |
224 elif code == errors.EXTRA_SPACE: | |
225 if error.position: | |
226 token.string = error.position.Set(token.string, '') | |
227 self._AddFix(token) | |
228 | |
229 elif code == errors.MISSING_LINE: | |
230 if error.position.IsAtBeginning(): | |
231 tokenutil.InsertBlankLineAfter(token.previous) | |
232 else: | |
233 tokenutil.InsertBlankLineAfter(token) | |
234 self._AddFix(token) | |
235 | |
236 elif code == errors.EXTRA_LINE: | |
237 self._DeleteToken(token) | |
238 self._AddFix(token) | |
239 | |
240 elif code == errors.WRONG_BLANK_LINE_COUNT: | |
241 if not token.previous: | |
242 # TODO(user): Add an insertBefore method to tokenutil. | |
243 return | |
244 | |
245 num_lines = error.fix_data | |
246 should_delete = False | |
247 | |
248 if num_lines < 0: | |
249 num_lines *= -1 | |
250 should_delete = True | |
251 | |
252 for unused_i in xrange(1, num_lines + 1): | |
253 if should_delete: | |
254 # TODO(user): DeleteToken should update line numbers. | |
255 self._DeleteToken(token.previous) | |
256 else: | |
257 tokenutil.InsertBlankLineAfter(token.previous) | |
258 self._AddFix(token) | |
259 | |
260 elif code == errors.UNNECESSARY_DOUBLE_QUOTED_STRING: | |
261 end_quote = tokenutil.Search(token, Type.DOUBLE_QUOTE_STRING_END) | |
262 if end_quote: | |
263 single_quote_start = Token( | |
264 "'", Type.SINGLE_QUOTE_STRING_START, token.line, token.line_number) | |
265 single_quote_end = Token( | |
266 "'", Type.SINGLE_QUOTE_STRING_START, end_quote.line, | |
267 token.line_number) | |
268 | |
269 tokenutil.InsertTokenAfter(single_quote_start, token) | |
270 tokenutil.InsertTokenAfter(single_quote_end, end_quote) | |
271 self._DeleteToken(token) | |
272 self._DeleteToken(end_quote) | |
273 self._AddFix([token, end_quote]) | |
274 | |
275 elif code == errors.MISSING_BRACES_AROUND_TYPE: | |
276 fixed_tokens = [] | |
277 start_token = token.attached_object.type_start_token | |
278 | |
279 if start_token.type != Type.DOC_START_BRACE: | |
280 leading_space = ( | |
281 len(start_token.string) - len(start_token.string.lstrip())) | |
282 if leading_space: | |
283 start_token = tokenutil.SplitToken(start_token, leading_space) | |
284 # Fix case where start and end token were the same. | |
285 if token.attached_object.type_end_token == start_token.previous: | |
286 token.attached_object.type_end_token = start_token | |
287 | |
288 new_token = Token('{', Type.DOC_START_BRACE, start_token.line, | |
289 start_token.line_number) | |
290 tokenutil.InsertTokenAfter(new_token, start_token.previous) | |
291 token.attached_object.type_start_token = new_token | |
292 fixed_tokens.append(new_token) | |
293 | |
294 end_token = token.attached_object.type_end_token | |
295 if end_token.type != Type.DOC_END_BRACE: | |
296 # If the start token was a brace, the end token will be a | |
297 # FLAG_ENDING_TYPE token, if there wasn't a starting brace then | |
298 # the end token is the last token of the actual type. | |
299 last_type = end_token | |
300 if not fixed_tokens: | |
301 last_type = end_token.previous | |
302 | |
303 while last_type.string.isspace(): | |
304 last_type = last_type.previous | |
305 | |
306 # If there was no starting brace then a lone end brace wouldn't have | |
307 # been type end token. Now that we've added any missing start brace, | |
308 # see if the last effective type token was an end brace. | |
309 if last_type.type != Type.DOC_END_BRACE: | |
310 trailing_space = (len(last_type.string) - | |
311 len(last_type.string.rstrip())) | |
312 if trailing_space: | |
313 tokenutil.SplitToken(last_type, | |
314 len(last_type.string) - trailing_space) | |
315 | |
316 new_token = Token('}', Type.DOC_END_BRACE, last_type.line, | |
317 last_type.line_number) | |
318 tokenutil.InsertTokenAfter(new_token, last_type) | |
319 token.attached_object.type_end_token = new_token | |
320 fixed_tokens.append(new_token) | |
321 | |
322 self._AddFix(fixed_tokens) | |
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 | |
361 elif code == errors.GOOG_REQUIRES_NOT_ALPHABETIZED: | |
362 require_start_token = error.fix_data | |
363 sorter = requireprovidesorter.RequireProvideSorter() | |
364 sorter.FixRequires(require_start_token) | |
365 | |
366 self._AddFix(require_start_token) | |
367 | |
368 elif code == errors.GOOG_PROVIDES_NOT_ALPHABETIZED: | |
369 provide_start_token = error.fix_data | |
370 sorter = requireprovidesorter.RequireProvideSorter() | |
371 sorter.FixProvides(provide_start_token) | |
372 | |
373 self._AddFix(provide_start_token) | |
374 | |
375 elif code == errors.UNNECESSARY_BRACES_AROUND_INHERIT_DOC: | |
376 if token.previous.string == '{' and token.next.string == '}': | |
377 self._DeleteToken(token.previous) | |
378 self._DeleteToken(token.next) | |
379 self._AddFix([token]) | |
380 | |
381 elif code == errors.INVALID_AUTHOR_TAG_DESCRIPTION: | |
382 match = INVERTED_AUTHOR_SPEC.match(token.string) | |
383 if match: | |
384 token.string = '%s%s%s(%s)%s' % (match.group('leading_whitespace'), | |
385 match.group('email'), | |
386 match.group('whitespace_after_name'), | |
387 match.group('name'), | |
388 match.group('trailing_characters')) | |
389 self._AddFix(token) | |
390 | |
391 elif (code == errors.WRONG_INDENTATION and | |
392 not FLAGS.disable_indentation_fixing): | |
393 token = tokenutil.GetFirstTokenInSameLine(token) | |
394 actual = error.position.start | |
395 expected = error.position.length | |
396 | |
397 # Cases where first token is param but with leading spaces. | |
398 if (len(token.string.lstrip()) == len(token.string) - actual and | |
399 token.string.lstrip()): | |
400 token.string = token.string.lstrip() | |
401 actual = 0 | |
402 | |
403 if token.type in (Type.WHITESPACE, Type.PARAMETERS) and actual != 0: | |
404 token.string = token.string.lstrip() + (' ' * expected) | |
405 self._AddFix([token]) | |
406 else: | |
407 # We need to add indentation. | |
408 new_token = Token(' ' * expected, Type.WHITESPACE, | |
409 token.line, token.line_number) | |
410 # Note that we'll never need to add indentation at the first line, | |
411 # since it will always not be indented. Therefore it's safe to assume | |
412 # token.previous exists. | |
413 tokenutil.InsertTokenAfter(new_token, token.previous) | |
414 self._AddFix([token]) | |
415 | |
416 elif code in [errors.MALFORMED_END_OF_SCOPE_COMMENT, | |
417 errors.MISSING_END_OF_SCOPE_COMMENT]: | |
418 # Only fix cases where }); is found with no trailing content on the line | |
419 # other than a comment. Value of 'token' is set to } for this error. | |
420 if (token.type == Type.END_BLOCK and | |
421 token.next.type == Type.END_PAREN and | |
422 token.next.next.type == Type.SEMICOLON): | |
423 current_token = token.next.next.next | |
424 removed_tokens = [] | |
425 while current_token and current_token.line_number == token.line_number: | |
426 if current_token.IsAnyType(Type.WHITESPACE, | |
427 Type.START_SINGLE_LINE_COMMENT, | |
428 Type.COMMENT): | |
429 removed_tokens.append(current_token) | |
430 current_token = current_token.next | |
431 else: | |
432 return | |
433 | |
434 if removed_tokens: | |
435 self._DeleteTokens(removed_tokens[0], len(removed_tokens)) | |
436 | |
437 whitespace_token = Token(' ', Type.WHITESPACE, token.line, | |
438 token.line_number) | |
439 start_comment_token = Token('//', Type.START_SINGLE_LINE_COMMENT, | |
440 token.line, token.line_number) | |
441 comment_token = Token(' goog.scope', Type.COMMENT, token.line, | |
442 token.line_number) | |
443 insertion_tokens = [whitespace_token, start_comment_token, | |
444 comment_token] | |
445 | |
446 tokenutil.InsertTokensAfter(insertion_tokens, token.next.next) | |
447 self._AddFix(removed_tokens + insertion_tokens) | |
448 | |
449 elif code in [errors.EXTRA_GOOG_PROVIDE, errors.EXTRA_GOOG_REQUIRE]: | |
450 tokens_in_line = tokenutil.GetAllTokensInSameLine(token) | |
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) | |
459 self._AddFix(tokens_in_line) | |
460 | |
461 elif code in [errors.MISSING_GOOG_PROVIDE, errors.MISSING_GOOG_REQUIRE]: | |
462 missing_namespaces = error.fix_data[0] | |
463 need_blank_line = error.fix_data[1] or (not token.previous) | |
464 | |
465 insert_location = Token('', Type.NORMAL, '', token.line_number - 1) | |
466 dummy_first_token = insert_location | |
467 tokenutil.InsertTokenBefore(insert_location, token) | |
468 | |
469 # If inserting a blank line check blank line does not exist before | |
470 # token to avoid extra blank lines. | |
471 if (need_blank_line and insert_location.previous | |
472 and insert_location.previous.type != Type.BLANK_LINE): | |
473 tokenutil.InsertBlankLineAfter(insert_location) | |
474 insert_location = insert_location.next | |
475 | |
476 for missing_namespace in missing_namespaces: | |
477 new_tokens = self._GetNewRequireOrProvideTokens( | |
478 code == errors.MISSING_GOOG_PROVIDE, | |
479 missing_namespace, insert_location.line_number + 1) | |
480 tokenutil.InsertLineAfter(insert_location, new_tokens) | |
481 insert_location = new_tokens[-1] | |
482 self._AddFix(new_tokens) | |
483 | |
484 # If inserting a blank line check blank line does not exist after | |
485 # token to avoid extra blank lines. | |
486 if (need_blank_line and insert_location.next | |
487 and insert_location.next.type != Type.BLANK_LINE): | |
488 tokenutil.InsertBlankLineAfter(insert_location) | |
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 | |
504 def _GetNewRequireOrProvideTokens(self, is_provide, namespace, line_number): | |
505 """Returns a list of tokens to create a goog.require/provide statement. | |
506 | |
507 Args: | |
508 is_provide: True if getting tokens for a provide, False for require. | |
509 namespace: The required or provided namespaces to get tokens for. | |
510 line_number: The line number the new require or provide statement will be | |
511 on. | |
512 | |
513 Returns: | |
514 Tokens to create a new goog.require or goog.provide statement. | |
515 """ | |
516 string = 'goog.require' | |
517 if is_provide: | |
518 string = 'goog.provide' | |
519 line_text = string + '(\'' + namespace + '\');\n' | |
520 return [ | |
521 Token(string, Type.IDENTIFIER, line_text, line_number), | |
522 Token('(', Type.START_PAREN, line_text, line_number), | |
523 Token('\'', Type.SINGLE_QUOTE_STRING_START, line_text, line_number), | |
524 Token(namespace, Type.STRING_TEXT, line_text, line_number), | |
525 Token('\'', Type.SINGLE_QUOTE_STRING_END, line_text, line_number), | |
526 Token(')', Type.END_PAREN, line_text, line_number), | |
527 Token(';', Type.SEMICOLON, line_text, line_number) | |
528 ] | |
529 | |
530 def _DeleteToken(self, token): | |
531 """Deletes the specified token from the linked list of tokens. | |
532 | |
533 Updates instance variables pointing to tokens such as _file_token if | |
534 they reference the deleted token. | |
535 | |
536 Args: | |
537 token: The token to delete. | |
538 """ | |
539 if token == self._file_token: | |
540 self._file_token = token.next | |
541 | |
542 tokenutil.DeleteToken(token) | |
543 | |
544 def _DeleteTokens(self, token, token_count): | |
545 """Deletes the given number of tokens starting with the given token. | |
546 | |
547 Updates instance variables pointing to tokens such as _file_token if | |
548 they reference the deleted token. | |
549 | |
550 Args: | |
551 token: The first token to delete. | |
552 token_count: The total number of tokens to delete. | |
553 """ | |
554 if token == self._file_token: | |
555 for unused_i in xrange(token_count): | |
556 self._file_token = self._file_token.next | |
557 | |
558 tokenutil.DeleteTokens(token, token_count) | |
559 | |
560 def FinishFile(self): | |
561 """Called when the current file has finished style checking. | |
562 | |
563 Used to go back and fix any errors in the file. It currently supports both | |
564 js and html files. For js files it does a simple dump of all tokens, but in | |
565 order to support html file, we need to merge the original file with the new | |
566 token set back together. This works because the tokenized html file is the | |
567 original html file with all non js lines kept but blanked out with one blank | |
568 line token per line of html. | |
569 """ | |
570 if self._file_fix_count: | |
571 # Get the original file content for html. | |
572 if self._file_is_html: | |
573 f = open(self._file_name, 'r') | |
574 original_lines = f.readlines() | |
575 f.close() | |
576 | |
577 f = self._external_file | |
578 if not f: | |
579 error_noun = 'error' if self._file_fix_count == 1 else 'errors' | |
580 print 'Fixed %d %s in %s' % ( | |
581 self._file_fix_count, error_noun, self._file_name) | |
582 f = open(self._file_name, 'w') | |
583 | |
584 token = self._file_token | |
585 # Finding the first not deleted token. | |
586 while token.is_deleted: | |
587 token = token.next | |
588 # If something got inserted before first token (e.g. due to sorting) | |
589 # then move to start. Bug 8398202. | |
590 while token.previous: | |
591 token = token.previous | |
592 char_count = 0 | |
593 line = '' | |
594 while token: | |
595 line += token.string | |
596 char_count += len(token.string) | |
597 | |
598 if token.IsLastInLine(): | |
599 # We distinguish if a blank line in html was from stripped original | |
600 # file or newly added error fix by looking at the "org_line_number" | |
601 # field on the token. It is only set in the tokenizer, so for all | |
602 # error fixes, the value should be None. | |
603 if (line or not self._file_is_html or | |
604 token.orig_line_number is None): | |
605 f.write(line) | |
606 f.write('\n') | |
607 else: | |
608 f.write(original_lines[token.orig_line_number - 1]) | |
609 line = '' | |
610 if char_count > 80 and token.line_number in self._file_changed_lines: | |
611 print 'WARNING: Line %d of %s is now longer than 80 characters.' % ( | |
612 token.line_number, self._file_name) | |
613 | |
614 char_count = 0 | |
615 | |
616 token = token.next | |
617 | |
618 if not self._external_file: | |
619 # Close the file if we created it | |
620 f.close() | |
OLD | NEW |