eric6/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleChecker.py

changeset 6942
2602857055c5
parent 6789
6bafe4f7d5f0
child 7242
799f9ec0d4e6
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2011 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the code style checker.
8 """
9
10 try: # Only for Py2
11 import Queue as queue
12 except ImportError:
13 import queue
14
15 import sys
16 import multiprocessing
17
18 import pycodestyle
19 from NamingStyleChecker import NamingStyleChecker
20
21 # register the name checker
22 pycodestyle.register_check(NamingStyleChecker, NamingStyleChecker.Codes)
23
24 from DocStyleChecker import DocStyleChecker
25 from MiscellaneousChecker import MiscellaneousChecker
26 from ComplexityChecker import ComplexityChecker
27
28
29 def initService():
30 """
31 Initialize the service and return the entry point.
32
33 @return the entry point for the background client (function)
34 """
35 return codeStyleCheck
36
37
38 def initBatchService():
39 """
40 Initialize the batch service and return the entry point.
41
42 @return the entry point for the background client (function)
43 """
44 return codeStyleBatchCheck
45
46
47 class CodeStyleCheckerReport(pycodestyle.BaseReport):
48 """
49 Class implementing a special report to be used with our dialog.
50 """
51 def __init__(self, options):
52 """
53 Constructor
54
55 @param options options for the report (optparse.Values)
56 """
57 super(CodeStyleCheckerReport, self).__init__(options)
58
59 self.__repeat = options.repeat
60 self.errors = []
61
62 def error_args(self, line_number, offset, code, check, *args):
63 """
64 Public method to collect the error messages.
65
66 @param line_number line number of the issue (integer)
67 @param offset position within line of the issue (integer)
68 @param code message code (string)
69 @param check reference to the checker function (function)
70 @param args arguments for the message (list)
71 @return error code (string)
72 """
73 code = super(CodeStyleCheckerReport, self).error_args(
74 line_number, offset, code, check, *args)
75 if code and (self.counters[code] == 1 or self.__repeat):
76 self.errors.append(
77 (self.filename, line_number, offset, (code, args))
78 )
79 return code
80
81
82 def extractLineFlags(line, startComment="#", endComment="", flagsLine=False):
83 """
84 Function to extract flags starting and ending with '__' from a line
85 comment.
86
87 @param line line to extract flags from (string)
88 @keyparam startComment string identifying the start of the comment (string)
89 @keyparam endComment string identifying the end of a comment (string)
90 @keyparam flagsLine flag indicating to check for a flags only line (bool)
91 @return list containing the extracted flags (list of strings)
92 """
93 flags = []
94
95 if not flagsLine or (
96 flagsLine and line.strip().startswith(startComment)):
97 pos = line.rfind(startComment)
98 if pos >= 0:
99 comment = line[pos + len(startComment):].strip()
100 if endComment:
101 endPos = line.rfind(endComment)
102 if endPos >= 0:
103 comment = comment[:endPos]
104 flags = [f.strip() for f in comment.split()
105 if (f.startswith("__") and f.endswith("__"))]
106 flags += [f.strip().lower() for f in comment.split()
107 if f in ("noqa", "NOQA")]
108 return flags
109
110
111 def ignoreCode(code, lineFlags):
112 """
113 Function to check, if the given code should be ignored as per line flags.
114
115 @param code error code to be checked
116 @type str
117 @param lineFlags list of line flags to check against
118 @type list of str
119 @return flag indicating to ignore the code
120 @rtype bool
121 """
122 if lineFlags:
123
124 if "__IGNORE_WARNING__" in lineFlags or \
125 "noqa" in lineFlags:
126 # ignore all warning codes
127 return True
128
129 for flag in lineFlags:
130 # check individual warning code
131 if flag.startswith("__IGNORE_WARNING_"):
132 ignoredCode = flag[2:-2].rsplit("_", 1)[-1]
133 if code.startswith(ignoredCode):
134 return True
135
136 return False
137
138
139 def codeStyleCheck(filename, source, args):
140 """
141 Do the code style check and/or fix found errors.
142
143 @param filename source filename
144 @type str
145 @param source string containing the code to check
146 @type str
147 @param args arguments used by the codeStyleCheck function (list of
148 excludeMessages, includeMessages, repeatMessages, fixCodes,
149 noFixCodes, fixIssues, maxLineLength, maxDocLineLength, blankLines,
150 hangClosing, docType, codeComplexityArgs, miscellaneousArgs, errors,
151 eol, encoding, backup)
152 @type list of (str, str, bool, str, str, bool, int, list of (int, int),
153 bool, str, dict, dict, list of str, str, str, bool)
154 @return tuple of statistics (dict) and list of results (tuple for each
155 found violation of style (lineno, position, text, ignored, fixed,
156 autofixing, fixedMsg))
157 @rtype tuple of (dict, list of tuples of (int, int, str, bool, bool, bool,
158 str))
159 """
160 return __checkCodeStyle(filename, source, args)
161
162
163 def codeStyleBatchCheck(argumentsList, send, fx, cancelled, maxProcesses=0):
164 """
165 Module function to check code style for a batch of files.
166
167 @param argumentsList list of arguments tuples as given for codeStyleCheck
168 @type list
169 @param send reference to send function
170 @type func
171 @param fx registered service name
172 @type str
173 @param cancelled reference to function checking for a cancellation
174 @type func
175 @param maxProcesses number of processes to be used
176 @type int
177 """
178 if maxProcesses == 0:
179 # determine based on CPU count
180 try:
181 NumberOfProcesses = multiprocessing.cpu_count()
182 if NumberOfProcesses >= 1:
183 NumberOfProcesses -= 1
184 except NotImplementedError:
185 NumberOfProcesses = 1
186 else:
187 NumberOfProcesses = maxProcesses
188
189 # Create queues
190 taskQueue = multiprocessing.Queue()
191 doneQueue = multiprocessing.Queue()
192
193 # Submit tasks (initially two time number of processes
194 initialTasks = 2 * NumberOfProcesses
195 for task in argumentsList[:initialTasks]:
196 taskQueue.put(task)
197
198 # Start worker processes
199 for _ in range(NumberOfProcesses):
200 multiprocessing.Process(target=worker, args=(taskQueue, doneQueue))\
201 .start()
202
203 # Get and send results
204 endIndex = len(argumentsList) - initialTasks
205 for i in range(len(argumentsList)):
206 resultSent = False
207 wasCancelled = False
208
209 while not resultSent:
210 try:
211 # get result (waiting max. 3 seconds and send it to frontend
212 filename, result = doneQueue.get(timeout=3)
213 send(fx, filename, result)
214 resultSent = True
215 except queue.Empty:
216 # ignore empty queue, just carry on
217 if cancelled():
218 wasCancelled = True
219 break
220
221 if wasCancelled or cancelled():
222 # just exit the loop ignoring the results of queued tasks
223 break
224
225 if i < endIndex:
226 taskQueue.put(argumentsList[i + initialTasks])
227
228 # Tell child processes to stop
229 for _ in range(NumberOfProcesses):
230 taskQueue.put('STOP')
231
232
233 def worker(inputQueue, outputQueue):
234 """
235 Module function acting as the parallel worker for the style check.
236
237 @param inputQueue input queue (multiprocessing.Queue)
238 @param outputQueue output queue (multiprocessing.Queue)
239 """
240 for filename, source, args in iter(inputQueue.get, 'STOP'):
241 result = __checkCodeStyle(filename, source, args)
242 outputQueue.put((filename, result))
243
244
245 def __checkCodeStyle(filename, source, args):
246 """
247 Private module function to perform the code style check and/or fix
248 found errors.
249
250 @param filename source filename
251 @type str
252 @param source string containing the code to check
253 @type str
254 @param args arguments used by the codeStyleCheck function (list of
255 excludeMessages, includeMessages, repeatMessages, fixCodes,
256 noFixCodes, fixIssues, maxLineLength, maxDocLineLength, blankLines,
257 hangClosing, docType, codeComplexityArgs, miscellaneousArgs, errors,
258 eol, encoding, backup)
259 @type list of (str, str, bool, str, str, bool, int, list of (int, int),
260 bool, str, dict, dict, list of str, str, str, bool)
261 @return tuple of statistics (dict) and list of results (tuple for each
262 found violation of style (lineno, position, text, ignored, fixed,
263 autofixing, fixedMsg))
264 @rtype tuple of (dict, list of tuples of (int, int, str, bool, bool, bool,
265 str))
266 """
267 (excludeMessages, includeMessages, repeatMessages, fixCodes, noFixCodes,
268 fixIssues, maxLineLength, maxDocLineLength, blankLines, hangClosing,
269 docType, codeComplexityArgs, miscellaneousArgs, errors, eol, encoding,
270 backup) = args
271
272 stats = {}
273
274 if fixIssues:
275 from CodeStyleFixer import CodeStyleFixer
276 fixer = CodeStyleFixer(
277 filename, source, fixCodes, noFixCodes,
278 maxLineLength, blankLines, True, eol, backup)
279 # always fix in place
280 else:
281 fixer = None
282
283 if not errors:
284 # avoid 'Encoding declaration in unicode string' exception on Python2
285 if sys.version_info[0] == 2:
286 if encoding == 'utf-8-bom':
287 enc = 'utf-8'
288 else:
289 enc = encoding
290 source = [line.encode(enc) for line in source]
291
292 if includeMessages:
293 select = [s.strip() for s in
294 includeMessages.split(',') if s.strip()]
295 else:
296 select = []
297 if excludeMessages:
298 ignore = [i.strip() for i in
299 excludeMessages.split(',') if i.strip()]
300 else:
301 ignore = []
302
303 # check coding style
304 pycodestyle.BLANK_LINES_CONFIG = {
305 # Top level class and function.
306 'top_level': blankLines[0],
307 # Methods and nested class and function.
308 'method': blankLines[1],
309 }
310 styleGuide = pycodestyle.StyleGuide(
311 reporter=CodeStyleCheckerReport,
312 repeat=repeatMessages,
313 select=select,
314 ignore=ignore,
315 max_line_length=maxLineLength,
316 max_doc_length=maxDocLineLength,
317 hang_closing=hangClosing,
318 )
319 report = styleGuide.check_files([filename])
320 stats.update(report.counters)
321 errors = report.errors
322
323 # check documentation style
324 docStyleChecker = DocStyleChecker(
325 source, filename, select, ignore, [], repeatMessages,
326 maxLineLength=maxDocLineLength, docType=docType)
327 docStyleChecker.run()
328 stats.update(docStyleChecker.counters)
329 errors += docStyleChecker.errors
330
331 # miscellaneous additional checks
332 miscellaneousChecker = MiscellaneousChecker(
333 source, filename, select, ignore, [], repeatMessages,
334 miscellaneousArgs)
335 miscellaneousChecker.run()
336 stats.update(miscellaneousChecker.counters)
337 errors += miscellaneousChecker.errors
338
339 # check code complexity
340 complexityChecker = ComplexityChecker(
341 source, filename, select, ignore, codeComplexityArgs)
342 complexityChecker.run()
343 stats.update(complexityChecker.counters)
344 errors += complexityChecker.errors
345
346 errorsDict = {}
347 for _fname, lineno, position, text in errors:
348 if lineno > len(source):
349 lineno = len(source)
350 # inverse processing of messages and fixes
351 errorLine = errorsDict.setdefault(lineno, [])
352 errorLine.append([position, text])
353 deferredFixes = {}
354 results = []
355 for lineno, errors in errorsDict.items():
356 errors.sort(key=lambda x: x[0], reverse=True)
357 for position, text in errors:
358 if source:
359 code = text[0]
360 lineFlags = extractLineFlags(source[lineno - 1].strip())
361 try:
362 lineFlags += extractLineFlags(source[lineno].strip(),
363 flagsLine=True)
364 except IndexError:
365 pass
366 if not ignoreCode(code, lineFlags):
367 if fixer:
368 res, msg, id_ = fixer.fixIssue(lineno, position, text)
369 if res == -1:
370 itm = [lineno, position, text]
371 deferredFixes[id_] = itm
372 else:
373 itm = [lineno, position, text, False,
374 res == 1, True, msg]
375 else:
376 itm = [lineno, position, text, False,
377 False, False, '']
378 results.append(itm)
379 else:
380 results.append([lineno, position, text, True,
381 False, False, ''])
382 else:
383 results.append([lineno, position, text, False,
384 False, False, ''])
385
386 if fixer:
387 deferredResults = fixer.finalize()
388 for id_ in deferredResults:
389 fixed, msg = deferredResults[id_]
390 itm = deferredFixes[id_]
391 itm.extend([False, fixed == 1, True, msg])
392
393 errMsg = fixer.saveFile(encoding)
394 if errMsg:
395 for result in results:
396 result[-1] = errMsg
397
398 return stats, results
399
400 #
401 # eflag: noqa = M702

eric ide

mercurial