266 |
266 |
267 def saveFile(self, encoding): |
267 def saveFile(self, encoding): |
268 """ |
268 """ |
269 Public method to save the modified file. |
269 Public method to save the modified file. |
270 |
270 |
271 @param encoding encoding of the source file (string) |
271 @param encoding encoding of the source file |
272 @return error message on failure (tuple of str) |
272 @type str |
|
273 @return error message on failure |
|
274 @rtype tuple of (str, [str]) |
273 """ |
275 """ |
274 if not self.__modified: |
276 if not self.__modified: |
275 # no need to write |
277 # no need to write |
276 return None |
278 return None |
277 |
279 |
303 |
305 |
304 def __codeMatch(self, code): |
306 def __codeMatch(self, code): |
305 """ |
307 """ |
306 Private method to check, if the code should be fixed. |
308 Private method to check, if the code should be fixed. |
307 |
309 |
308 @param code to check (string) |
310 @param code to check |
309 @return flag indicating it should be fixed (boolean) |
311 @type str |
|
312 @return flag indicating it should be fixed |
|
313 @rtype bool |
310 """ |
314 """ |
311 |
315 |
312 def mutualStartswith(a, b): |
316 def mutualStartswith(a, b): |
313 """ |
317 """ |
314 Local helper method to compare the beginnings of two strings |
318 Local helper method to compare the beginnings of two strings |
315 against each other. |
319 against each other. |
316 |
320 |
317 @return flag indicating that one string starts with the other |
321 @return flag indicating that one string starts with the other |
318 (boolean) |
322 @rtype bool |
319 """ |
323 """ |
320 return b.startswith(a) or a.startswith(b) |
324 return b.startswith(a) or a.startswith(b) |
321 |
325 |
322 if self.__noFixCodes and any( |
326 if self.__noFixCodes and any( |
323 mutualStartswith(code.lower(), noFixCode.lower()) |
327 mutualStartswith(code.lower(), noFixCode.lower()) |
365 def finalize(self): |
369 def finalize(self): |
366 """ |
370 """ |
367 Public method to apply all deferred fixes. |
371 Public method to apply all deferred fixes. |
368 |
372 |
369 @return dictionary containing the fix results |
373 @return dictionary containing the fix results |
|
374 @rtype dict |
370 """ |
375 """ |
371 results = {} |
376 results = {} |
372 |
377 |
373 # step 1: do fixes operating on logical lines first |
378 # step 1: do fixes operating on logical lines first |
374 for id_, code, line, pos in self.__stackLogical: |
379 for id_, code, line, pos in self.__stackLogical: |
436 def __getLogical(self, line, pos): |
443 def __getLogical(self, line, pos): |
437 """ |
444 """ |
438 Private method to get the logical line corresponding to the given |
445 Private method to get the logical line corresponding to the given |
439 position. |
446 position. |
440 |
447 |
441 @param line line number of the issue (integer) |
448 @param line line number of the issue |
442 @param pos position inside line (integer) |
449 @type int |
|
450 @param pos position inside line |
|
451 @type int |
443 @return tuple of a tuple of two integers giving the start of the |
452 @return tuple of a tuple of two integers giving the start of the |
444 logical line, another tuple of two integers giving the end |
453 logical line, another tuple of two integers giving the end |
445 of the logical line and a list of strings with the original |
454 of the logical line and a list of strings with the original |
446 source lines |
455 source lines |
|
456 @rtype tuple of ((int, int), (int, int), [str]) |
447 """ |
457 """ |
448 try: |
458 try: |
449 (logical_start, logical_end) = self.__findLogical() |
459 (logical_start, logical_end) = self.__findLogical() |
450 except (SyntaxError, tokenize.TokenError): |
460 except (SyntaxError, tokenize.TokenError): |
451 return None |
461 return None |
467 |
477 |
468 def __getIndentWord(self): |
478 def __getIndentWord(self): |
469 """ |
479 """ |
470 Private method to determine the indentation type. |
480 Private method to determine the indentation type. |
471 |
481 |
472 @return string to be used for an indentation (string) |
482 @return string to be used for an indentation |
|
483 @rtype str |
473 """ |
484 """ |
474 sio = StringIO("".join(self.__source)) |
485 sio = StringIO("".join(self.__source)) |
475 indentWord = " " # default in case of failure |
486 indentWord = " " # default in case of failure |
476 with contextlib.suppress(SyntaxError, tokenize.TokenError): |
487 with contextlib.suppress(SyntaxError, tokenize.TokenError): |
477 for token in tokenize.generate_tokens(sio.readline): |
488 for token in tokenize.generate_tokens(sio.readline): |
482 |
493 |
483 def __getIndent(self, line): |
494 def __getIndent(self, line): |
484 """ |
495 """ |
485 Private method to get the indentation string. |
496 Private method to get the indentation string. |
486 |
497 |
487 @param line line to determine the indentation string from (string) |
498 @param line line to determine the indentation string from |
488 @return indentation string (string) |
499 @type str |
|
500 @return indentation string |
|
501 @rtype str |
489 """ |
502 """ |
490 return line.replace(line.lstrip(), "") |
503 return line.replace(line.lstrip(), "") |
491 |
504 |
492 def __multilineStringLines(self): |
505 def __multilineStringLines(self): |
493 """ |
506 """ |
494 Private method to determine the line numbers that are within multi line |
507 Private method to determine the line numbers that are within multi line |
495 strings and these which are part of a documentation string. |
508 strings and these which are part of a documentation string. |
496 |
509 |
497 @return tuple of a set of line numbers belonging to a multi line |
510 @return tuple of a set of line numbers belonging to a multi line |
498 string and a set of line numbers belonging to a multi line |
511 string and a set of line numbers belonging to a multi line |
499 documentation string (tuple of two set of integer) |
512 documentation string |
|
513 @rtype tuple of (set of int, set of int) |
500 """ |
514 """ |
501 if self.__multiLineNumbers is None: |
515 if self.__multiLineNumbers is None: |
502 source = "".join(self.__source) |
516 source = "".join(self.__source) |
503 sio = StringIO(source) |
517 sio = StringIO(source) |
504 self.__multiLineNumbers = set() |
518 self.__multiLineNumbers = set() |
524 """ |
538 """ |
525 Private method to fix a badly indented line. |
539 Private method to fix a badly indented line. |
526 |
540 |
527 This is done by adding or removing from its initial indent only. |
541 This is done by adding or removing from its initial indent only. |
528 |
542 |
529 @param line line number of the issue (integer) |
543 @param line line number of the issue |
530 @param pos position inside line (integer) |
544 @type int |
|
545 @param pos position inside line |
|
546 @type int |
531 @param logical logical line structure |
547 @param logical logical line structure |
532 @return flag indicating a change was done (boolean) |
548 @type str |
|
549 @return flag indicating a change was done |
|
550 @rtype bool |
533 @exception ValueError raised to indicate a bad 'logical' parameter |
551 @exception ValueError raised to indicate a bad 'logical' parameter |
534 """ |
552 """ |
535 if not logical: |
553 if not logical: |
536 raise ValueError("Bad value for 'logical' parameter.") |
554 raise ValueError("Bad value for 'logical' parameter.") |
537 |
555 |
568 |
586 |
569 def __fixWhitespace(self, line, offset, replacement): |
587 def __fixWhitespace(self, line, offset, replacement): |
570 """ |
588 """ |
571 Private method to correct whitespace at the given offset. |
589 Private method to correct whitespace at the given offset. |
572 |
590 |
573 @param line line to be corrected (string) |
591 @param line line to be corrected |
574 @param offset offset within line (integer) |
592 @type str |
575 @param replacement replacement string (string) |
593 @param offset offset within line |
|
594 @type int |
|
595 @param replacement replacement string |
|
596 @type str |
576 @return corrected line |
597 @return corrected line |
|
598 @rtype str |
577 """ |
599 """ |
578 left = line[:offset].rstrip(" \t") |
600 left = line[:offset].rstrip(" \t") |
579 right = line[offset:].lstrip(" \t") |
601 right = line[offset:].lstrip(" \t") |
580 if right.startswith("#"): |
602 if right.startswith("#"): |
581 return line |
603 return line |
2202 |
2225 |
2203 def fixedLine(self, line): |
2226 def fixedLine(self, line): |
2204 """ |
2227 """ |
2205 Public method to get a fixed line. |
2228 Public method to get a fixed line. |
2206 |
2229 |
2207 @param line number of the line to retrieve (integer) |
2230 @param line number of the line to retrieve |
2208 @return fixed line (string) |
2231 @type int |
|
2232 @return fixed line |
|
2233 @rtype str |
2209 """ |
2234 """ |
2210 if line < len(self.after): |
2235 if line < len(self.after): |
2211 return self.after[line] |
2236 return self.after[line] |
2212 |
2237 |
2213 return "" |
2238 return "" |
2214 |
2239 |
2215 def getline(self): |
2240 def getline(self): |
2216 """ |
2241 """ |
2217 Public method to get a line of text for tokenize. |
2242 Public method to get a line of text for tokenize. |
2218 |
2243 |
2219 @return line of text (string) |
2244 @return line of text |
|
2245 @rtype str |
2220 """ |
2246 """ |
2221 if self.index >= len(self.lines): |
2247 if self.index >= len(self.lines): |
2222 line = "" |
2248 line = "" |
2223 else: |
2249 else: |
2224 line = self.lines[self.index] |
2250 line = self.lines[self.index] |
2228 def __genStats(self, tokens): |
2254 def __genStats(self, tokens): |
2229 """ |
2255 """ |
2230 Private method to generate the re-indent statistics. |
2256 Private method to generate the re-indent statistics. |
2231 |
2257 |
2232 @param tokens tokens generator (tokenize._tokenize) |
2258 @param tokens tokens generator (tokenize._tokenize) |
|
2259 @type function |
2233 @return reference to the generated statistics |
2260 @return reference to the generated statistics |
|
2261 @rtype dict |
2234 """ |
2262 """ |
2235 find_stmt = True # next token begins a fresh stmt? |
2263 find_stmt = True # next token begins a fresh stmt? |
2236 level = 0 # current indent level |
2264 level = 0 # current indent level |
2237 stats = [] |
2265 stats = [] |
2238 |
2266 |
2276 |
2304 |
2277 def __getlspace(self, line): |
2305 def __getlspace(self, line): |
2278 """ |
2306 """ |
2279 Private method to count number of leading blanks. |
2307 Private method to count number of leading blanks. |
2280 |
2308 |
2281 @param line line to check (string) |
2309 @param line line to check |
2282 @return number of leading blanks (integer) |
2310 @type str |
|
2311 @return number of leading blanks |
|
2312 @rtype int |
2283 """ |
2313 """ |
2284 i = 0 |
2314 i = 0 |
2285 n = len(line) |
2315 n = len(line) |
2286 while i < n and line[i] == " ": |
2316 while i < n and line[i] == " ": |
2287 i += 1 |
2317 i += 1 |
2309 def __init__(self, physical_lines): |
2339 def __init__(self, physical_lines): |
2310 """ |
2340 """ |
2311 Constructor |
2341 Constructor |
2312 |
2342 |
2313 @param physical_lines list of physical lines to operate on |
2343 @param physical_lines list of physical lines to operate on |
2314 (list of strings) |
2344 @type list of str |
2315 """ |
2345 """ |
2316 self.lines = physical_lines |
2346 self.lines = physical_lines |
2317 self.tokens = [] |
2347 self.tokens = [] |
2318 self.rel_indent = None |
2348 self.rel_indent = None |
2319 sio = StringIO("".join(physical_lines)) |
2349 sio = StringIO("".join(physical_lines)) |
2328 def __buildTokensLogical(self, tokens): |
2358 def __buildTokensLogical(self, tokens): |
2329 """ |
2359 """ |
2330 Private method to build a logical line from a list of tokens. |
2360 Private method to build a logical line from a list of tokens. |
2331 |
2361 |
2332 @param tokens list of tokens as generated by tokenize.generate_tokens |
2362 @param tokens list of tokens as generated by tokenize.generate_tokens |
2333 @return logical line (string) |
2363 @type list of Token |
|
2364 @return logical line |
|
2365 @rtype str |
2334 """ |
2366 """ |
2335 # from pycodestyle.py with minor modifications |
2367 # from pycodestyle.py with minor modifications |
2336 logical = [] |
2368 logical = [] |
2337 previous = None |
2369 previous = None |
2338 for t in tokens: |
2370 for t in tokens: |
2360 """ |
2392 """ |
2361 Public method to replicate logic in pycodestyle.py, to know what level |
2393 Public method to replicate logic in pycodestyle.py, to know what level |
2362 to indent things to. |
2394 to indent things to. |
2363 |
2395 |
2364 @return list of lists, where each list represents valid indent levels |
2396 @return list of lists, where each list represents valid indent levels |
2365 for the line in question, relative from the initial indent. However, |
2397 for the line in question, relative from the initial indent. However, |
2366 the first entry is the indent level which was expected. |
2398 the first entry is the indent level which was expected. |
|
2399 @rtype list of list |
2367 """ |
2400 """ |
2368 # What follows is an adjusted version of |
2401 # What follows is an adjusted version of |
2369 # pycodestyle.py:continuation_line_indentation. All of the comments |
2402 # pycodestyle.py:continuation_line_indentation. All of the comments |
2370 # have been stripped and the 'yield' statements replaced with 'pass'. |
2403 # have been stripped and the 'yield' statements replaced with 'pass'. |
2371 if not self.tokens: |
2404 if not self.tokens: |
2537 isDocString=False, |
2570 isDocString=False, |
2538 ): |
2571 ): |
2539 """ |
2572 """ |
2540 Constructor |
2573 Constructor |
2541 |
2574 |
2542 @param curLine text to work on (string) |
2575 @param curLine text to work on |
2543 @param prevLine line before the text to work on (string) |
2576 @type str |
2544 @param nextLine line after the text to work on (string) |
2577 @param prevLine line before the text to work on |
2545 @param maxLength maximum allowed line length (integer) |
2578 @type str |
2546 @param eol eond-of-line marker (string) |
2579 @param nextLine line after the text to work on |
2547 @param indentWord string used for indentation (string) |
2580 @type str |
|
2581 @param maxLength maximum allowed line length |
|
2582 @type int |
|
2583 @param eol eond-of-line marker |
|
2584 @type str |
|
2585 @param indentWord string used for indentation |
|
2586 @type str |
2548 @param isDocString flag indicating that the line belongs to |
2587 @param isDocString flag indicating that the line belongs to |
2549 a documentation string (boolean) |
2588 a documentation string |
|
2589 @type bool |
2550 """ |
2590 """ |
2551 self.__text = curLine |
2591 self.__text = curLine |
2552 self.__prevText = prevLine |
2592 self.__prevText = prevLine |
2553 self.__nextText = nextLine |
2593 self.__nextText = nextLine |
2554 self.__maxLength = maxLength |
2594 self.__maxLength = maxLength |
2559 def shorten(self): |
2599 def shorten(self): |
2560 """ |
2600 """ |
2561 Public method to shorten the line wrapped by the class instance. |
2601 Public method to shorten the line wrapped by the class instance. |
2562 |
2602 |
2563 @return tuple of a flag indicating successful shortening, the |
2603 @return tuple of a flag indicating successful shortening, the |
2564 shortened line and the changed next line (boolean, string, string) |
2604 shortened line and the changed next line |
|
2605 @rtype tuple of (bool, str, str) |
2565 """ |
2606 """ |
2566 # 1. check for comment |
2607 # 1. check for comment |
2567 if self.__text.lstrip().startswith("#"): |
2608 if self.__text.lstrip().startswith("#"): |
2568 lastComment = True |
2609 lastComment = True |
2569 if self.__nextText.lstrip().startswith("#"): |
2610 if self.__nextText.lstrip().startswith("#"): |
2720 def __shortenComment(self, isLast): |
2761 def __shortenComment(self, isLast): |
2721 """ |
2762 """ |
2722 Private method to shorten a comment line. |
2763 Private method to shorten a comment line. |
2723 |
2764 |
2724 @param isLast flag indicating, that the line is the last comment line |
2765 @param isLast flag indicating, that the line is the last comment line |
2725 (boolean) |
2766 @type bool |
2726 @return shortened comment line (string) |
2767 @return shortened comment line |
|
2768 @rtype str |
2727 """ |
2769 """ |
2728 if len(self.__text) <= self.__maxLength: |
2770 if len(self.__text) <= self.__maxLength: |
2729 return self.__text |
2771 return self.__text |
2730 |
2772 |
2731 newText = self.__text.rstrip() |
2773 newText = self.__text.rstrip() |
2757 def __breakMultiline(self): |
2799 def __breakMultiline(self): |
2758 """ |
2800 """ |
2759 Private method to break multi line strings. |
2801 Private method to break multi line strings. |
2760 |
2802 |
2761 @return tuple of the shortened line and the changed next line |
2803 @return tuple of the shortened line and the changed next line |
2762 (string, string) |
2804 @rtype tuple of (str, str) |
2763 """ |
2805 """ |
2764 indentation = self.__getIndent(self.__text) |
2806 indentation = self.__getIndent(self.__text) |
2765 |
2807 |
2766 # Handle special case. |
2808 # Handle special case. |
2767 for symbol in "([{": |
2809 for symbol in "([{": |
2819 def __isProbablyInsideStringOrComment(self, line, index): |
2861 def __isProbablyInsideStringOrComment(self, line, index): |
2820 """ |
2862 """ |
2821 Private method to check, if the given string might be inside a string |
2863 Private method to check, if the given string might be inside a string |
2822 or comment. |
2864 or comment. |
2823 |
2865 |
2824 @param line line to check (string) |
2866 @param line line to check |
2825 @param index position inside line to check (integer) |
2867 @type str |
|
2868 @param index position inside line to check |
|
2869 @type int |
2826 @return flag indicating the possibility of being inside a string |
2870 @return flag indicating the possibility of being inside a string |
2827 or comment |
2871 or comment |
|
2872 @rtype bool |
2828 """ |
2873 """ |
2829 # Check against being in a string. |
2874 # Check against being in a string. |
2830 for quote in ['"', "'"]: |
2875 for quote in ['"', "'"]: |
2831 pos = line.find(quote) |
2876 pos = line.find(quote) |
2832 if pos != -1 and pos <= index: |
2877 if pos != -1 and pos <= index: |
2842 def __shortenLine(self, tokens, source, indent): |
2887 def __shortenLine(self, tokens, source, indent): |
2843 """ |
2888 """ |
2844 Private method to shorten a line of code at an operator. |
2889 Private method to shorten a line of code at an operator. |
2845 |
2890 |
2846 @param tokens tokens of the line as generated by tokenize |
2891 @param tokens tokens of the line as generated by tokenize |
2847 (list of token) |
2892 @type list of Token |
2848 @param source code string to work at (string) |
2893 @param source code string to work at |
2849 @param indent indentation string of the code line (string) |
2894 @type str |
2850 @return list of candidates (list of string) |
2895 @param indent indentation string of the code line |
|
2896 @type str |
|
2897 @return list of candidates |
|
2898 @rtype list of str |
2851 """ |
2899 """ |
2852 candidates = [] |
2900 candidates = [] |
2853 |
2901 |
2854 for tkn in tokens: |
2902 for tkn in tokens: |
2855 tokenType = tkn[0] |
2903 tokenType = tkn[0] |
2909 def __normalizeMultiline(self, text): |
2957 def __normalizeMultiline(self, text): |
2910 """ |
2958 """ |
2911 Private method to remove multiline-related code that will cause syntax |
2959 Private method to remove multiline-related code that will cause syntax |
2912 error. |
2960 error. |
2913 |
2961 |
2914 @param text code line to work on (string) |
2962 @param text code line to work on |
2915 @return normalized code line (string) |
2963 @type str |
|
2964 @return normalized code line |
|
2965 @rtype str |
2916 """ |
2966 """ |
2917 for quote in "'\"": |
2967 for quote in "'\"": |
2918 dictPattern = r"^{q}[^{q}]*{q} *: *".format(q=quote) |
2968 dictPattern = r"^{q}[^{q}]*{q} *: *".format(q=quote) |
2919 if re.match(dictPattern, text): |
2969 if re.match(dictPattern, text): |
2920 if not text.strip().endswith("}"): |
2970 if not text.strip().endswith("}"): |
2931 |
2981 |
2932 def __lineShorteningRank(self, candidate): |
2982 def __lineShorteningRank(self, candidate): |
2933 """ |
2983 """ |
2934 Private method to rank a candidate. |
2984 Private method to rank a candidate. |
2935 |
2985 |
2936 @param candidate candidate line to rank (string) |
2986 @param candidate candidate line to rank |
2937 @return rank of the candidate (integer) |
2987 @type str |
|
2988 @return rank of the candidate |
|
2989 @rtype int |
2938 """ |
2990 """ |
2939 rank = 0 |
2991 rank = 0 |
2940 if candidate.strip(): |
2992 if candidate.strip(): |
2941 if candidate == self.__text: |
2993 if candidate == self.__text: |
2942 # give the original a disadvantage |
2994 # give the original a disadvantage |
2994 def __countUnbalancedBrackets(self, line): |
3046 def __countUnbalancedBrackets(self, line): |
2995 """ |
3047 """ |
2996 Private method to determine the number of unmatched open/close |
3048 Private method to determine the number of unmatched open/close |
2997 brackets. |
3049 brackets. |
2998 |
3050 |
2999 @param line line to work at (string) |
3051 @param line line to work at |
3000 @return number of unmatched open/close brackets (integer) |
3052 @type str |
|
3053 @return number of unmatched open/close brackets |
|
3054 @rtype int |
3001 """ |
3055 """ |
3002 count = 0 |
3056 count = 0 |
3003 for opening, closing in ["()", "[]", "{}"]: |
3057 for opening, closing in ["()", "[]", "{}"]: |
3004 # __IGNORE_WARNING_M613__ |
3058 # __IGNORE_WARNING_M613__ |
3005 count += abs(line.count(opening) - line.count(closing)) |
3059 count += abs(line.count(opening) - line.count(closing)) |
3008 |
3062 |
3009 def __getIndent(self, line): |
3063 def __getIndent(self, line): |
3010 """ |
3064 """ |
3011 Private method to get the indentation string. |
3065 Private method to get the indentation string. |
3012 |
3066 |
3013 @param line line to determine the indentation string from (string) |
3067 @param line line to determine the indentation string from |
3014 @return indentation string (string) |
3068 @type str |
|
3069 @return indentation string |
|
3070 @rtype str |
3015 """ |
3071 """ |
3016 # copied from CodeStyleFixer |
3072 # copied from CodeStyleFixer |
3017 return line.replace(line.lstrip(), "") |
3073 return line.replace(line.lstrip(), "") |
3018 |
3074 |
3019 def __checkSyntax(self, code): |
3075 def __checkSyntax(self, code): |
3020 """ |
3076 """ |
3021 Private method to check the syntax of the given code fragment. |
3077 Private method to check the syntax of the given code fragment. |
3022 |
3078 |
3023 @param code code fragment to check (string) |
3079 @param code code fragment to check |
3024 @return flag indicating syntax is ok (boolean) |
3080 @type str |
|
3081 @return flag indicating syntax is ok |
|
3082 @rtype bool |
3025 """ |
3083 """ |
3026 code = code.replace("\r\n", "\n").replace("\r", "\n") |
3084 code = code.replace("\r\n", "\n").replace("\r", "\n") |
3027 try: |
3085 try: |
3028 return compile(code, "<string>", "exec") |
3086 return compile(code, "<string>", "exec") |
3029 except (SyntaxError, TypeError, UnicodeDecodeError): |
3087 except (SyntaxError, TypeError, UnicodeDecodeError): |