19 from pyflakes.messages import ImportStarUsed, ImportStarUsage |
19 from pyflakes.messages import ImportStarUsed, ImportStarUsage |
20 |
20 |
21 VcsConflictMarkerRegExpList = ( |
21 VcsConflictMarkerRegExpList = ( |
22 re.compile( |
22 re.compile( |
23 r"""^<<<<<<< .*?\|\|\|\|\|\|\| .*?=======.*?>>>>>>> .*?$""", |
23 r"""^<<<<<<< .*?\|\|\|\|\|\|\| .*?=======.*?>>>>>>> .*?$""", |
24 re.MULTILINE | re.DOTALL |
24 re.MULTILINE | re.DOTALL, |
25 ), |
25 ), |
26 re.compile( |
26 re.compile(r"""^<<<<<<< .*?=======.*?>>>>>>> .*?$""", re.MULTILINE | re.DOTALL), |
27 r"""^<<<<<<< .*?=======.*?>>>>>>> .*?$""", |
|
28 re.MULTILINE | re.DOTALL |
|
29 ), |
|
30 ) |
27 ) |
31 |
28 |
32 |
29 |
33 def initService(): |
30 def initService(): |
34 """ |
31 """ |
35 Initialize the service and return the entry point. |
32 Initialize the service and return the entry point. |
36 |
33 |
37 @return the entry point for the background client (function) |
34 @return the entry point for the background client (function) |
38 """ |
35 """ |
39 return syntaxAndPyflakesCheck |
36 return syntaxAndPyflakesCheck |
40 |
37 |
41 |
38 |
42 def initBatchService(): |
39 def initBatchService(): |
43 """ |
40 """ |
44 Initialize the batch service and return the entry point. |
41 Initialize the batch service and return the entry point. |
45 |
42 |
46 @return the entry point for the background client (function) |
43 @return the entry point for the background client (function) |
47 """ |
44 """ |
48 return syntaxAndPyflakesBatchCheck |
45 return syntaxAndPyflakesBatchCheck |
49 |
46 |
50 |
47 |
51 def normalizeCode(codestring): |
48 def normalizeCode(codestring): |
52 """ |
49 """ |
53 Function to normalize the given code. |
50 Function to normalize the given code. |
54 |
51 |
55 @param codestring code to be normalized (string) |
52 @param codestring code to be normalized (string) |
56 @return normalized code (string) |
53 @return normalized code (string) |
57 """ |
54 """ |
58 codestring = codestring.replace("\r\n", "\n").replace("\r", "\n") |
55 codestring = codestring.replace("\r\n", "\n").replace("\r", "\n") |
59 |
56 |
60 if codestring and codestring[-1] != '\n': |
57 if codestring and codestring[-1] != "\n": |
61 codestring += '\n' |
58 codestring += "\n" |
62 |
59 |
63 return codestring |
60 return codestring |
64 |
61 |
65 |
62 |
66 def extractLineFlags(line, startComment="#", endComment="", flagsLine=False): |
63 def extractLineFlags(line, startComment="#", endComment="", flagsLine=False): |
67 """ |
64 """ |
68 Function to extract flags starting and ending with '__' from a line |
65 Function to extract flags starting and ending with '__' from a line |
69 comment. |
66 comment. |
70 |
67 |
71 @param line line to extract flags from (string) |
68 @param line line to extract flags from (string) |
72 @param startComment string identifying the start of the comment (string) |
69 @param startComment string identifying the start of the comment (string) |
73 @param endComment string identifying the end of a comment (string) |
70 @param endComment string identifying the end of a comment (string) |
74 @param flagsLine flag indicating to check for a flags only line (bool) |
71 @param flagsLine flag indicating to check for a flags only line (bool) |
75 @return list containing the extracted flags (list of strings) |
72 @return list containing the extracted flags (list of strings) |
76 """ |
73 """ |
77 flags = [] |
74 flags = [] |
78 |
75 |
79 if not flagsLine or ( |
76 if not flagsLine or (flagsLine and line.strip().startswith(startComment)): |
80 flagsLine and line.strip().startswith(startComment)): |
|
81 pos = line.rfind(startComment) |
77 pos = line.rfind(startComment) |
82 if pos >= 0: |
78 if pos >= 0: |
83 comment = line[pos + len(startComment):].strip() |
79 comment = line[pos + len(startComment) :].strip() |
84 if endComment: |
80 if endComment: |
85 endPos = line.rfind(endComment) |
81 endPos = line.rfind(endComment) |
86 if endPos >= 0: |
82 if endPos >= 0: |
87 comment = comment[:endPos] |
83 comment = comment[:endPos] |
88 flags = [f.strip() for f in comment.split() |
84 flags = [ |
89 if (f.startswith("__") and f.endswith("__"))] |
85 f.strip() |
90 flags += [f.strip().lower() for f in comment.split() |
86 for f in comment.split() |
91 if f in ("noqa", "NOQA")] |
87 if (f.startswith("__") and f.endswith("__")) |
|
88 ] |
|
89 flags += [ |
|
90 f.strip().lower() for f in comment.split() if f in ("noqa", "NOQA") |
|
91 ] |
92 return flags |
92 return flags |
93 |
93 |
94 |
94 |
95 def syntaxAndPyflakesCheck(filename, codestring, checkFlakes=True, |
95 def syntaxAndPyflakesCheck( |
96 ignoreStarImportWarnings=False): |
96 filename, codestring, checkFlakes=True, ignoreStarImportWarnings=False |
|
97 ): |
97 """ |
98 """ |
98 Function to compile one Python source file to Python bytecode |
99 Function to compile one Python source file to Python bytecode |
99 and to perform a pyflakes check. |
100 and to perform a pyflakes check. |
100 |
101 |
101 @param filename source filename (string) |
102 @param filename source filename (string) |
102 @param codestring string containing the code to compile (string) |
103 @param codestring string containing the code to compile (string) |
103 @param checkFlakes flag indicating to do a pyflakes check (boolean) |
104 @param checkFlakes flag indicating to do a pyflakes check (boolean) |
104 @param ignoreStarImportWarnings flag indicating to |
105 @param ignoreStarImportWarnings flag indicating to |
105 ignore 'star import' warnings (boolean) |
106 ignore 'star import' warnings (boolean) |
106 @return dictionary with the keys 'error' and 'warnings' which |
107 @return dictionary with the keys 'error' and 'warnings' which |
107 hold a list containing details about the error/ warnings |
108 hold a list containing details about the error/ warnings |
108 (file name, line number, column, codestring (only at syntax |
109 (file name, line number, column, codestring (only at syntax |
109 errors), the message, a list with arguments for the message) |
110 errors), the message, a list with arguments for the message) |
110 """ |
111 """ |
111 return __syntaxAndPyflakesCheck(filename, codestring, checkFlakes, |
112 return __syntaxAndPyflakesCheck( |
112 ignoreStarImportWarnings) |
113 filename, codestring, checkFlakes, ignoreStarImportWarnings |
113 |
114 ) |
114 |
115 |
115 def syntaxAndPyflakesBatchCheck(argumentsList, send, fx, cancelled, |
116 |
116 maxProcesses=0): |
117 def syntaxAndPyflakesBatchCheck(argumentsList, send, fx, cancelled, maxProcesses=0): |
117 """ |
118 """ |
118 Module function to check syntax for a batch of files. |
119 Module function to check syntax for a batch of files. |
119 |
120 |
120 @param argumentsList list of arguments tuples as given for |
121 @param argumentsList list of arguments tuples as given for |
121 syntaxAndPyflakesCheck |
122 syntaxAndPyflakesCheck |
122 @type list |
123 @type list |
123 @param send reference to send function |
124 @param send reference to send function |
124 @type func |
125 @type func |
173 except queue.Empty: |
173 except queue.Empty: |
174 # ignore empty queue, just carry on |
174 # ignore empty queue, just carry on |
175 if cancelled(): |
175 if cancelled(): |
176 wasCancelled = True |
176 wasCancelled = True |
177 break |
177 break |
178 |
178 |
179 if wasCancelled or cancelled(): |
179 if wasCancelled or cancelled(): |
180 # just exit the loop ignoring the results of queued tasks |
180 # just exit the loop ignoring the results of queued tasks |
181 break |
181 break |
182 |
182 |
183 if i < endIndex: |
183 if i < endIndex: |
184 taskQueue.put(argumentsList[i + initialTasks]) |
184 taskQueue.put(argumentsList[i + initialTasks]) |
185 |
185 |
186 # Tell child processes to stop |
186 # Tell child processes to stop |
187 for _ in range(NumberOfProcesses): |
187 for _ in range(NumberOfProcesses): |
188 taskQueue.put('STOP') |
188 taskQueue.put("STOP") |
189 |
189 |
190 for worker in workers: |
190 for worker in workers: |
191 worker.join() |
191 worker.join() |
192 worker.close() |
192 worker.close() |
193 |
193 |
194 |
194 |
195 def workerTask(inputQueue, outputQueue): |
195 def workerTask(inputQueue, outputQueue): |
196 """ |
196 """ |
197 Module function acting as the parallel worker for the syntax check. |
197 Module function acting as the parallel worker for the syntax check. |
198 |
198 |
199 @param inputQueue input queue (multiprocessing.Queue) |
199 @param inputQueue input queue (multiprocessing.Queue) |
200 @param outputQueue output queue (multiprocessing.Queue) |
200 @param outputQueue output queue (multiprocessing.Queue) |
201 """ |
201 """ |
202 for filename, args in iter(inputQueue.get, 'STOP'): |
202 for filename, args in iter(inputQueue.get, "STOP"): |
203 source, checkFlakes, ignoreStarImportWarnings = args |
203 source, checkFlakes, ignoreStarImportWarnings = args |
204 result = __syntaxAndPyflakesCheck(filename, source, checkFlakes, |
204 result = __syntaxAndPyflakesCheck( |
205 ignoreStarImportWarnings) |
205 filename, source, checkFlakes, ignoreStarImportWarnings |
|
206 ) |
206 outputQueue.put((filename, result)) |
207 outputQueue.put((filename, result)) |
207 |
208 |
208 |
209 |
209 def __syntaxAndPyflakesCheck(filename, codestring, checkFlakes=True, |
210 def __syntaxAndPyflakesCheck( |
210 ignoreStarImportWarnings=False): |
211 filename, codestring, checkFlakes=True, ignoreStarImportWarnings=False |
|
212 ): |
211 """ |
213 """ |
212 Function to compile one Python source file to Python bytecode |
214 Function to compile one Python source file to Python bytecode |
213 and to perform a pyflakes check. |
215 and to perform a pyflakes check. |
214 |
216 |
215 @param filename source filename |
217 @param filename source filename |
216 @type str |
218 @type str |
217 @param codestring string containing the code to compile |
219 @param codestring string containing the code to compile |
218 @type str |
220 @type str |
219 @param checkFlakes flag indicating to do a pyflakes check |
221 @param checkFlakes flag indicating to do a pyflakes check |
226 (file name, line number, column, codestring (only at syntax |
228 (file name, line number, column, codestring (only at syntax |
227 errors), the message, a list with arguments for the message) |
229 errors), the message, a list with arguments for the message) |
228 @rtype dict |
230 @rtype dict |
229 """ |
231 """ |
230 import builtins |
232 import builtins |
231 |
233 |
232 try: |
234 try: |
233 codestring = normalizeCode(codestring) |
235 codestring = normalizeCode(codestring) |
234 |
236 |
235 # Check for VCS conflict markers |
237 # Check for VCS conflict markers |
236 for conflictMarkerRe in VcsConflictMarkerRegExpList: |
238 for conflictMarkerRe in VcsConflictMarkerRegExpList: |
237 conflict = conflictMarkerRe.search(codestring) |
239 conflict = conflictMarkerRe.search(codestring) |
238 if conflict is not None: |
240 if conflict is not None: |
239 start, i = conflict.span() |
241 start, i = conflict.span() |
240 lineindex = 1 + codestring.count("\n", 0, start) |
242 lineindex = 1 + codestring.count("\n", 0, start) |
241 return [{'error': |
243 return [ |
242 (filename, lineindex, 0, "", |
244 {"error": (filename, lineindex, 0, "", "VCS conflict marker found")} |
243 "VCS conflict marker found") |
245 ] |
244 }] |
246 |
245 |
247 if filename.endswith(".ptl"): |
246 if filename.endswith('.ptl'): |
|
247 try: |
248 try: |
248 import quixote.ptl_compile |
249 import quixote.ptl_compile |
249 except ImportError: |
250 except ImportError: |
250 return [{'error': (filename, 0, 0, '', |
251 return [{"error": (filename, 0, 0, "", "Quixote plugin not found.")}] |
251 'Quixote plugin not found.')}] |
|
252 template = quixote.ptl_compile.Template(codestring, filename) |
252 template = quixote.ptl_compile.Template(codestring, filename) |
253 template.compile() |
253 template.compile() |
254 else: |
254 else: |
255 module = builtins.compile( |
255 module = builtins.compile(codestring, filename, "exec", ast.PyCF_ONLY_AST) |
256 codestring, filename, 'exec', ast.PyCF_ONLY_AST) |
|
257 except SyntaxError as detail: |
256 except SyntaxError as detail: |
258 index = 0 |
257 index = 0 |
259 code = "" |
258 code = "" |
260 error = "" |
259 error = "" |
261 lines = traceback.format_exception_only(SyntaxError, detail) |
260 lines = traceback.format_exception_only(SyntaxError, detail) |
262 match = re.match(r'\s*File "(.+)", line (\d+)', |
261 match = re.match( |
263 lines[0].replace('<string>', filename)) |
262 r'\s*File "(.+)", line (\d+)', lines[0].replace("<string>", filename) |
|
263 ) |
264 if match is not None: |
264 if match is not None: |
265 fn, line = match.group(1, 2) |
265 fn, line = match.group(1, 2) |
266 if lines[1].startswith('SyntaxError:'): |
266 if lines[1].startswith("SyntaxError:"): |
267 error = re.match('SyntaxError: (.+)', lines[1]).group(1) |
267 error = re.match("SyntaxError: (.+)", lines[1]).group(1) |
268 else: |
268 else: |
269 code = re.match('(.+)', lines[1]).group(1) |
269 code = re.match("(.+)", lines[1]).group(1) |
270 for seLine in lines[2:]: |
270 for seLine in lines[2:]: |
271 if seLine.startswith('SyntaxError:'): |
271 if seLine.startswith("SyntaxError:"): |
272 error = re.match('SyntaxError: (.+)', seLine).group(1) |
272 error = re.match("SyntaxError: (.+)", seLine).group(1) |
273 elif seLine.rstrip().endswith('^'): |
273 elif seLine.rstrip().endswith("^"): |
274 index = len(seLine.rstrip()) - 4 |
274 index = len(seLine.rstrip()) - 4 |
275 else: |
275 else: |
276 fn = detail.filename |
276 fn = detail.filename |
277 line = detail.lineno or 1 |
277 line = detail.lineno or 1 |
278 error = detail.msg |
278 error = detail.msg |
279 return [{'error': (fn, int(line), index, code.strip(), error)}] |
279 return [{"error": (fn, int(line), index, code.strip(), error)}] |
280 except ValueError as detail: |
280 except ValueError as detail: |
281 try: |
281 try: |
282 fn = detail.filename |
282 fn = detail.filename |
283 line = detail.lineno |
283 line = detail.lineno |
284 error = detail.msg |
284 error = detail.msg |
285 except AttributeError: |
285 except AttributeError: |
286 fn = filename |
286 fn = filename |
287 line = 1 |
287 line = 1 |
288 error = str(detail) |
288 error = str(detail) |
289 return [{'error': (fn, line, 0, "", error)}] |
289 return [{"error": (fn, line, 0, "", error)}] |
290 except Exception as detail: |
290 except Exception as detail: |
291 with contextlib.suppress(Exception): |
291 with contextlib.suppress(Exception): |
292 fn = detail.filename |
292 fn = detail.filename |
293 line = detail.lineno |
293 line = detail.lineno |
294 error = detail.msg |
294 error = detail.msg |
295 return [{'error': (fn, line, 0, "", error)}] |
295 return [{"error": (fn, line, 0, "", error)}] |
296 |
296 |
297 # pyflakes |
297 # pyflakes |
298 if not checkFlakes: |
298 if not checkFlakes: |
299 return [{}] |
299 return [{}] |
300 |
300 |
301 results = [] |
301 results = [] |
302 lines = codestring.splitlines() |
302 lines = codestring.splitlines() |
303 try: |
303 try: |
304 warnings = Checker(module, filename, withDoctest=True) |
304 warnings = Checker(module, filename, withDoctest=True) |
305 warnings.messages.sort(key=lambda a: a.lineno) |
305 warnings.messages.sort(key=lambda a: a.lineno) |
306 for warning in warnings.messages: |
306 for warning in warnings.messages: |
307 if ( |
307 if ignoreStarImportWarnings and isinstance( |
308 ignoreStarImportWarnings and |
308 warning, (ImportStarUsed, ImportStarUsage) |
309 isinstance(warning, (ImportStarUsed, ImportStarUsage)) |
|
310 ): |
309 ): |
311 continue |
310 continue |
312 |
311 |
313 _fn, lineno, col, message, msg_args = warning.getMessageData() |
312 _fn, lineno, col, message, msg_args = warning.getMessageData() |
314 lineFlags = extractLineFlags(lines[lineno - 1].strip()) |
313 lineFlags = extractLineFlags(lines[lineno - 1].strip()) |
315 with contextlib.suppress(IndexError): |
314 with contextlib.suppress(IndexError): |
316 lineFlags += extractLineFlags(lines[lineno].strip(), |
315 lineFlags += extractLineFlags(lines[lineno].strip(), flagsLine=True) |
317 flagsLine=True) |
316 if "__IGNORE_WARNING__" not in lineFlags and "noqa" not in lineFlags: |
318 if ( |
|
319 "__IGNORE_WARNING__" not in lineFlags and |
|
320 "noqa" not in lineFlags |
|
321 ): |
|
322 results.append((_fn, lineno, col, "", message, msg_args)) |
317 results.append((_fn, lineno, col, "", message, msg_args)) |
323 except SyntaxError as err: |
318 except SyntaxError as err: |
324 msg = err.text.strip() if err.text.strip() else err.msg |
319 msg = err.text.strip() if err.text.strip() else err.msg |
325 results.append((filename, err.lineno, 0, "FLAKES_ERROR", msg, [])) |
320 results.append((filename, err.lineno, 0, "FLAKES_ERROR", msg, [])) |
326 |
321 |
327 return [{'warnings': results}] |
322 return [{"warnings": results}] |