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