src/eric7/Plugins/CheckerPlugins/SyntaxChecker/SyntaxCheck.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9279
e252f827aaa7
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
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
149 for task in argumentsList[:initialTasks]: 150 for task in argumentsList[:initialTasks]:
150 taskQueue.put(task) 151 taskQueue.put(task)
151 152
152 # Start worker processes 153 # Start worker processes
153 workers = [ 154 workers = [
154 multiprocessing.Process( 155 multiprocessing.Process(target=workerTask, args=(taskQueue, doneQueue))
155 target=workerTask, args=(taskQueue, doneQueue) 156 for _ in range(NumberOfProcesses)
156 ) for _ in range(NumberOfProcesses)
157 ] 157 ]
158 for worker in workers: 158 for worker in workers:
159 worker.start() 159 worker.start()
160 160
161 # Get and send results 161 # Get and send results
162 endIndex = len(argumentsList) - initialTasks 162 endIndex = len(argumentsList) - initialTasks
163 for i in range(len(argumentsList)): 163 for i in range(len(argumentsList)):
164 resultSent = False 164 resultSent = False
165 wasCancelled = False 165 wasCancelled = False
166 166
167 while not resultSent: 167 while not resultSent:
168 try: 168 try:
169 # get result (waiting max. 3 seconds and send it to frontend 169 # get result (waiting max. 3 seconds and send it to frontend
170 filename, result = doneQueue.get() 170 filename, result = doneQueue.get()
171 send(fx, filename, result) 171 send(fx, filename, result)
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}]

eric ide

mercurial