5 |
5 |
6 """ |
6 """ |
7 Module implementing the code style checker. |
7 Module implementing the code style checker. |
8 """ |
8 """ |
9 |
9 |
10 from __future__ import unicode_literals |
10 import sys |
11 |
11 |
12 try: |
12 import pep8 |
13 str = unicode # __IGNORE_WARNING__ |
13 from NamingStyleChecker import NamingStyleChecker |
14 except (NameError): |
|
15 pass |
|
16 |
14 |
17 import os |
15 # register the name checker |
|
16 pep8.register_check(NamingStyleChecker, NamingStyleChecker.Codes) |
18 |
17 |
19 from PyQt4.QtCore import QProcess, QCoreApplication |
18 from DocStyleChecker import DocStyleChecker |
20 |
|
21 from . import pep8 |
|
22 from .NamingStyleChecker import NamingStyleChecker |
|
23 from .DocStyleChecker import DocStyleChecker |
|
24 |
|
25 import Preferences |
|
26 import Utilities |
|
27 |
|
28 from eric5config import getConfig |
|
29 |
19 |
30 |
20 |
31 class CodeStyleCheckerPy2(object): |
21 def initService(): |
32 """ |
22 """ |
33 Class implementing the code style checker interface for Python 2. |
23 Initialize the service and return the entry point. |
|
24 |
|
25 @return the entry point for the background client (function) |
34 """ |
26 """ |
35 def __init__(self, filename, lines, repeat=False, |
27 return codeStyleCheck |
36 select="", ignore="", max_line_length=79, |
28 |
37 hang_closing=False, docType="pep257"): |
29 |
|
30 class CodeStyleCheckerReport(pep8.BaseReport): |
|
31 """ |
|
32 Class implementing a special report to be used with our dialog. |
|
33 """ |
|
34 def __init__(self, options): |
38 """ |
35 """ |
39 Constructor |
36 Constructor |
40 |
37 |
41 @param filename name of the file to check (string) |
38 @param options options for the report (optparse.Values) |
42 @param lines source of the file (list of strings) (ignored) |
|
43 @keyparam repeat flag indicating to repeat message categories (boolean) |
|
44 @keyparam select list of message IDs to check for |
|
45 (comma separated string) |
|
46 @keyparam ignore list of message IDs to ignore |
|
47 (comma separated string) |
|
48 @keyparam max_line_length maximum allowed line length (integer) |
|
49 @keyparam hang_closing flag indicating to allow hanging closing |
|
50 brackets (boolean) |
|
51 @keyparam docType type of the documentation strings |
|
52 (string, one of 'eric' or 'pep257') |
|
53 @exception AssertionError raised if the docType argument is not |
|
54 "eric" or "pep257" |
|
55 """ |
39 """ |
56 assert docType in ("eric", "pep257") |
40 super(CodeStyleCheckerReport, self).__init__(options) |
57 |
41 |
|
42 self.__repeat = options.repeat |
58 self.errors = [] |
43 self.errors = [] |
59 self.counters = {} |
44 |
|
45 def error_args(self, line_number, offset, code, check, *args): |
|
46 """ |
|
47 Public method to collect the error messages. |
60 |
48 |
61 interpreter = Preferences.getDebugger("PythonInterpreter") |
49 @param line_number line number of the issue (integer) |
62 if interpreter == "" or not Utilities.isExecutable(interpreter): |
50 @param offset position within line of the issue (integer) |
|
51 @param code message code (string) |
|
52 @param check reference to the checker function (function) |
|
53 @param args arguments for the message (list) |
|
54 @return error code (string) |
|
55 """ |
|
56 code = super(CodeStyleCheckerReport, self).error_args( |
|
57 line_number, offset, code, check, *args) |
|
58 if code and (self.counters[code] == 1 or self.__repeat): |
63 self.errors.append( |
59 self.errors.append( |
64 (filename, 1, 1, QCoreApplication.translate( |
60 (self.filename, line_number, offset, (code, args)) |
65 "CodeStyleCheckerPy2", |
61 ) |
66 "Python2 interpreter not configured."))) |
62 return code |
67 return |
63 |
|
64 |
|
65 def extractLineFlags(line, startComment="#", endComment=""): |
|
66 """ |
|
67 Function to extract flags starting and ending with '__' from a line |
|
68 comment. |
|
69 |
|
70 @param line line to extract flags from (string) |
|
71 @keyparam startComment string identifying the start of the comment (string) |
|
72 @keyparam endComment string identifying the end of a comment (string) |
|
73 @return list containing the extracted flags (list of strings) |
|
74 """ |
|
75 flags = [] |
|
76 |
|
77 pos = line.rfind(startComment) |
|
78 if pos >= 0: |
|
79 comment = line[pos + len(startComment):].strip() |
|
80 if endComment: |
|
81 comment = comment.replace("endComment", "") |
|
82 flags = [f.strip() for f in comment.split() |
|
83 if (f.startswith("__") and f.endswith("__"))] |
|
84 return flags |
|
85 |
|
86 |
|
87 def codeStyleCheck(filename, source, args): |
|
88 """ |
|
89 Do the code style check and/ or fix found errors. |
|
90 |
|
91 @param filename source filename (string) |
|
92 @param source string containing the code to check (string) |
|
93 @param args arguments used by the codeStyleCheck function (list of |
|
94 excludeMessages (str), includeMessages (str), repeatMessages |
|
95 (bool), fixCodes (str), noFixCodes (str), fixIssues (bool), |
|
96 maxLineLength (int), hangClosing (bool), docType (str), errors |
|
97 (list of str), eol (str), encoding (str)) |
|
98 @return tuple of stats (dict) and results (tuple for each found violation |
|
99 of style (tuple of lineno (int), position (int), text (str), fixed |
|
100 (bool), autofixing (bool), fixedMsg (str))) |
|
101 """ |
|
102 excludeMessages, includeMessages, \ |
|
103 repeatMessages, fixCodes, noFixCodes, fixIssues, maxLineLength, \ |
|
104 hangClosing, docType, errors, eol, encoding = args |
|
105 |
|
106 stats = {} |
|
107 # avoid 'Encoding declaration in unicode string' exception on Python2 |
|
108 if sys.version_info[0] == 2: |
|
109 if encoding == 'utf-8-bom': |
|
110 enc = 'utf-8' |
|
111 else: |
|
112 enc = encoding |
|
113 source = [line.encode(enc) for line in source] |
|
114 |
|
115 if fixIssues: |
|
116 from CodeStyleFixer import CodeStyleFixer |
|
117 fixer = CodeStyleFixer( |
|
118 filename, source, fixCodes, noFixCodes, |
|
119 maxLineLength, True, eol) # always fix in place |
|
120 else: |
|
121 fixer = None |
|
122 |
|
123 if not errors: |
|
124 if includeMessages: |
|
125 select = [s.strip() for s in |
|
126 includeMessages.split(',') if s.strip()] |
|
127 else: |
|
128 select = [] |
|
129 if excludeMessages: |
|
130 ignore = [i.strip() for i in |
|
131 excludeMessages.split(',') if i.strip()] |
|
132 else: |
|
133 ignore = [] |
68 |
134 |
69 checker = os.path.join(getConfig('ericDir'), |
135 # check coding style |
70 "UtilitiesPython2", "CodeStyleChecker.py") |
136 styleGuide = pep8.StyleGuide( |
|
137 reporter=CodeStyleCheckerReport, |
|
138 repeat=repeatMessages, |
|
139 select=select, |
|
140 ignore=ignore, |
|
141 max_line_length=maxLineLength, |
|
142 hang_closing=hangClosing, |
|
143 ) |
|
144 report = styleGuide.check_files([filename]) |
|
145 stats.update(report.counters) |
|
146 |
|
147 # check documentation style |
|
148 docStyleChecker = DocStyleChecker( |
|
149 source, filename, select, ignore, [], repeatMessages, |
|
150 maxLineLength=maxLineLength, docType=docType) |
|
151 docStyleChecker.run() |
|
152 stats.update(docStyleChecker.counters) |
71 |
153 |
72 args = [checker] |
154 errors = report.errors + docStyleChecker.errors |
73 if repeat: |
155 |
74 args.append("-r") |
156 deferredFixes = {} |
75 if select: |
157 results = [] |
76 args.append("-s") |
158 for fname, lineno, position, text in errors: |
77 args.append(select) |
159 if lineno > len(source): |
78 if ignore: |
160 lineno = len(source) |
79 args.append("-i") |
161 if "__IGNORE_WARNING__" not in \ |
80 args.append(ignore) |
162 extractLineFlags(source[lineno - 1].strip()): |
81 args.append("-m") |
163 if fixer: |
82 args.append(str(max_line_length)) |
164 res, msg, id_ = fixer.fixIssue(lineno, position, text) |
83 if hang_closing: |
165 if res == -1: |
84 args.append("-h") |
166 itm = [lineno, position, text] |
85 args.append("-d") |
167 deferredFixes[id_] = itm |
86 args.append(docType) |
|
87 args.append("-f") |
|
88 args.append(filename) |
|
89 |
|
90 proc = QProcess() |
|
91 proc.setProcessChannelMode(QProcess.MergedChannels) |
|
92 proc.start(interpreter, args) |
|
93 finished = proc.waitForFinished(15000) |
|
94 if finished: |
|
95 output = \ |
|
96 str(proc.readAllStandardOutput(), |
|
97 Preferences.getSystem("IOEncoding"), |
|
98 'replace').splitlines() |
|
99 if output[0] == "ERROR": |
|
100 self.errors.append((filename, 1, 1, output[2])) |
|
101 return |
|
102 |
|
103 if output[0] == "NO_PEP8": |
|
104 return |
|
105 |
|
106 index = 0 |
|
107 while index < len(output): |
|
108 if output[index] == "PEP8_STATISTICS": |
|
109 index += 1 |
|
110 break |
|
111 |
|
112 if output[index] == "EXCEPTION": |
|
113 exceptionText = os.linesep.join(output[index + 2:]) |
|
114 raise RuntimeError(exceptionText) |
|
115 |
|
116 fname = output[index + 1] |
|
117 lineno = int(output[index + 2]) |
|
118 position = int(output[index + 3]) |
|
119 code = output[index + 4] |
|
120 arglen = int(output[index + 5]) |
|
121 args = [] |
|
122 argindex = 0 |
|
123 while argindex < arglen: |
|
124 args.append(output[index + 6 + argindex]) |
|
125 argindex += 1 |
|
126 index += 6 + arglen |
|
127 |
|
128 if code in NamingStyleChecker.Codes: |
|
129 text = NamingStyleChecker.getMessage(code, *args) |
|
130 elif code in DocStyleChecker.Codes: |
|
131 text = DocStyleChecker.getMessage(code, *args) |
|
132 else: |
168 else: |
133 text = pep8.getMessage(code, *args) |
169 itm = [lineno, position, text, res == 1, True, msg] |
134 self.errors.append((fname, lineno, position, text)) |
170 else: |
135 while index < len(output): |
171 itm = [lineno, position, text, False, False, ''] |
136 code, countStr = output[index].split(None, 1) |
172 results.append(itm) |
137 self.counters[code] = int(countStr) |
173 |
138 index += 1 |
174 if fixer: |
139 else: |
175 deferredResults = fixer.finalize() |
140 self.errors.append( |
176 for id_ in deferredResults: |
141 (filename, 1, 1, QCoreApplication.translate( |
177 fixed, msg = deferredResults[id_] |
142 "CodeStyleCheckerPy2", |
178 itm = deferredFixes[id_] |
143 "Python2 interpreter did not finish within 15s."))) |
179 itm.extend([fixed == 1, fixed == 1, msg]) |
|
180 results.append(itm) |
|
181 |
|
182 errMsg = fixer.saveFile(encoding) |
|
183 if errMsg: |
|
184 for result in results: |
|
185 result[-1] = errMsg |
|
186 |
|
187 return stats, results |