eric6/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleChecker.py

branch
maintenance
changeset 8273
698ae46f40a4
parent 8176
31965986ecd1
parent 8259
2bbec88047dd
equal deleted inserted replaced
8190:fb0ef164f536 8273:698ae46f40a4
9 9
10 import queue 10 import queue
11 import ast 11 import ast
12 import sys 12 import sys
13 import multiprocessing 13 import multiprocessing
14 import contextlib
14 15
15 import pycodestyle 16 import pycodestyle
16 from Naming.NamingStyleChecker import NamingStyleChecker 17 from Naming.NamingStyleChecker import NamingStyleChecker
17 18
18 # register the name checker 19 # register the name checker
21 from DocStyle.DocStyleChecker import DocStyleChecker 22 from DocStyle.DocStyleChecker import DocStyleChecker
22 from Miscellaneous.MiscellaneousChecker import MiscellaneousChecker 23 from Miscellaneous.MiscellaneousChecker import MiscellaneousChecker
23 from Complexity.ComplexityChecker import ComplexityChecker 24 from Complexity.ComplexityChecker import ComplexityChecker
24 from Security.SecurityChecker import SecurityChecker 25 from Security.SecurityChecker import SecurityChecker
25 from PathLib.PathlibChecker import PathlibChecker 26 from PathLib.PathlibChecker import PathlibChecker
27 from Simplify.SimplifyChecker import SimplifyChecker
26 28
27 29
28 def initService(): 30 def initService():
29 """ 31 """
30 Initialize the service and return the entry point. 32 Initialize the service and return the entry point.
51 """ 53 """
52 Constructor 54 Constructor
53 55
54 @param options options for the report (optparse.Values) 56 @param options options for the report (optparse.Values)
55 """ 57 """
56 super(CodeStyleCheckerReport, self).__init__(options) 58 super().__init__(options)
57 59
58 self.__repeat = options.repeat 60 self.__repeat = options.repeat
59 self.errors = [] 61 self.errors = []
60 62
61 def error_args(self, line_number, offset, code, check, *args): 63 def error_args(self, line_number, offset, code, check, *args):
67 @param code message code (string) 69 @param code message code (string)
68 @param check reference to the checker function (function) 70 @param check reference to the checker function (function)
69 @param args arguments for the message (list) 71 @param args arguments for the message (list)
70 @return error code (string) 72 @return error code (string)
71 """ 73 """
72 code = super(CodeStyleCheckerReport, self).error_args( 74 code = super().error_args(
73 line_number, offset, code, check, *args) 75 line_number, offset, code, check, *args)
74 if code and (self.counters[code] == 1 or self.__repeat): 76 if code and (self.counters[code] == 1 or self.__repeat):
75 self.errors.append( 77 self.errors.append(
76 { 78 {
77 "file": self.filename, 79 "file": self.filename,
276 278
277 @param filename source filename 279 @param filename source filename
278 @type str 280 @type str
279 @param source string containing the code to check 281 @param source string containing the code to check
280 @type str 282 @type str
281 @return tuple containing the error dictionary with syntax error details 283 @return tuple containing the error dictionary with syntax error details,
282 and a statistics dictionary or a tuple containing two None 284 a statistics dictionary and None or a tuple containing two None and
283 @rtype tuple of (dict, dict) or tuple of (None, None) 285 the generated AST tree
286 @rtype tuple of (dict, dict, None) or tuple of (None, None, ast.Module)
284 """ 287 """
285 src = "".join(source) 288 src = "".join(source)
286 289
287 try: 290 try:
288 ast.parse(src, filename, 'exec') 291 tree = (
289 return None, None 292 ast.parse(src, filename, 'exec', type_comments=True)
293 # need the 'type_comments' parameter to include type annotations
294 if sys.version_info >= (3, 8) else
295 ast.parse(src, filename, 'exec')
296 )
297 return None, None, tree
290 except (SyntaxError, TypeError): 298 except (SyntaxError, TypeError):
291 exc_type, exc = sys.exc_info()[:2] 299 exc_type, exc = sys.exc_info()[:2]
292 if len(exc.args) > 1: 300 if len(exc.args) > 1:
293 offset = exc.args[1] 301 offset = exc.args[1]
294 if len(offset) > 2: 302 if len(offset) > 2:
295 offset = offset[1:3] 303 offset = offset[1:3]
296 else: 304 else:
297 offset = (1, 0) 305 offset = (1, 0)
298 return ({ 306 return (
299 "file": filename, 307 {
300 "line": offset[0], 308 "file": filename,
301 "offset": offset[1], 309 "line": offset[0],
302 "code": "E901", 310 "offset": offset[1],
303 "args": [exc_type.__name__, exc.args[0]], 311 "code": "E901",
304 }, { 312 "args": [exc_type.__name__, exc.args[0]],
305 "E901": 1, 313 }, {
306 }) 314 "E901": 1,
315 },
316 None
317 )
307 318
308 319
309 def __checkCodeStyle(filename, source, args): 320 def __checkCodeStyle(filename, source, args):
310 """ 321 """
311 Private module function to perform the code style check and/or fix 322 Private module function to perform the code style check and/or fix
364 ignore = [i.strip() for i in 375 ignore = [i.strip() for i in
365 excludeMessages.split(',') if i.strip()] 376 excludeMessages.split(',') if i.strip()]
366 else: 377 else:
367 ignore = [] 378 ignore = []
368 379
369 syntaxError, syntaxStats = __checkSyntax(filename, source) 380 syntaxError, syntaxStats, tree = __checkSyntax(filename, source)
370 if syntaxError: 381
371 errors = [syntaxError] 382 # perform the checks only, if syntax is ok and AST tree was generated
372 stats.update(syntaxStats) 383 if tree:
373
374 # perform the checks only, if syntax is ok
375 else:
376 # check coding style 384 # check coding style
377 pycodestyle.BLANK_LINES_CONFIG = { 385 pycodestyle.BLANK_LINES_CONFIG = {
378 # Top level class and function. 386 # Top level class and function.
379 'top_level': blankLines[0], 387 'top_level': blankLines[0],
380 # Methods and nested class and function. 388 # Methods and nested class and function.
401 stats.update(docStyleChecker.counters) 409 stats.update(docStyleChecker.counters)
402 errors += docStyleChecker.errors 410 errors += docStyleChecker.errors
403 411
404 # miscellaneous additional checks 412 # miscellaneous additional checks
405 miscellaneousChecker = MiscellaneousChecker( 413 miscellaneousChecker = MiscellaneousChecker(
406 source, filename, select, ignore, [], repeatMessages, 414 source, filename, tree, select, ignore, [], repeatMessages,
407 miscellaneousArgs) 415 miscellaneousArgs)
408 miscellaneousChecker.run() 416 miscellaneousChecker.run()
409 stats.update(miscellaneousChecker.counters) 417 stats.update(miscellaneousChecker.counters)
410 errors += miscellaneousChecker.errors 418 errors += miscellaneousChecker.errors
411 419
412 # check code complexity 420 # check code complexity
413 complexityChecker = ComplexityChecker( 421 complexityChecker = ComplexityChecker(
414 source, filename, select, ignore, codeComplexityArgs) 422 source, filename, tree, select, ignore, codeComplexityArgs)
415 complexityChecker.run() 423 complexityChecker.run()
416 stats.update(complexityChecker.counters) 424 stats.update(complexityChecker.counters)
417 errors += complexityChecker.errors 425 errors += complexityChecker.errors
418 426
419 # check function annotations 427 # check function annotations
420 if sys.version_info >= (3, 5, 0): 428 if sys.version_info >= (3, 8, 0):
421 # annotations are supported from Python 3.5 on 429 # annotations with type comments are supported from
430 # Python 3.8 on
422 from Annotations.AnnotationsChecker import AnnotationsChecker 431 from Annotations.AnnotationsChecker import AnnotationsChecker
423 annotationsChecker = AnnotationsChecker( 432 annotationsChecker = AnnotationsChecker(
424 source, filename, select, ignore, [], repeatMessages, 433 source, filename, tree, select, ignore, [], repeatMessages,
425 annotationArgs) 434 annotationArgs)
426 annotationsChecker.run() 435 annotationsChecker.run()
427 stats.update(annotationsChecker.counters) 436 stats.update(annotationsChecker.counters)
428 errors += annotationsChecker.errors 437 errors += annotationsChecker.errors
429 438
439 # check for security issues
430 securityChecker = SecurityChecker( 440 securityChecker = SecurityChecker(
431 source, filename, select, ignore, [], repeatMessages, 441 source, filename, tree, select, ignore, [], repeatMessages,
432 securityArgs) 442 securityArgs)
433 securityChecker.run() 443 securityChecker.run()
434 stats.update(securityChecker.counters) 444 stats.update(securityChecker.counters)
435 errors += securityChecker.errors 445 errors += securityChecker.errors
436 446
447 # check for pathlib usage
437 pathlibChecker = PathlibChecker( 448 pathlibChecker = PathlibChecker(
438 source, filename, select, ignore, [], repeatMessages) 449 source, filename, tree, select, ignore, [], repeatMessages)
439 pathlibChecker.run() 450 pathlibChecker.run()
440 stats.update(pathlibChecker.counters) 451 stats.update(pathlibChecker.counters)
441 errors += pathlibChecker.errors 452 errors += pathlibChecker.errors
453
454 # check for code simplifications
455 simplifyChecker = SimplifyChecker(
456 source, filename, tree, select, ignore, [], repeatMessages)
457 simplifyChecker.run()
458 stats.update(simplifyChecker.counters)
459 errors += simplifyChecker.errors
460
461 elif syntaxError:
462 errors = [syntaxError]
463 stats.update(syntaxStats)
442 464
443 errorsDict = {} 465 errorsDict = {}
444 for error in errors: 466 for error in errors:
445 if error["line"] > len(source): 467 if error["line"] > len(source):
446 error["line"] = len(source) 468 error["line"] = len(source)
462 }) 484 })
463 485
464 if source: 486 if source:
465 code = error["code"] 487 code = error["code"]
466 lineFlags = extractLineFlags(source[lineno - 1].strip()) 488 lineFlags = extractLineFlags(source[lineno - 1].strip())
467 try: 489 with contextlib.suppress(IndexError):
468 lineFlags += extractLineFlags(source[lineno].strip(), 490 lineFlags += extractLineFlags(source[lineno].strip(),
469 flagsLine=True) 491 flagsLine=True)
470 except IndexError:
471 pass
472 492
473 if securityOk(code, lineFlags): 493 if securityOk(code, lineFlags):
474 error["securityOk"] = True 494 error["securityOk"] = True
475 495
476 if ignoreCode(code, lineFlags): 496 if ignoreCode(code, lineFlags):

eric ide

mercurial