--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleChecker.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/CodeStyleChecker.py Wed Jul 13 14:55:47 2022 +0200 @@ -31,7 +31,7 @@ def initService(): """ Initialize the service and return the entry point. - + @return the entry point for the background client (function) """ return codeStyleCheck @@ -40,7 +40,7 @@ def initBatchService(): """ Initialize the batch service and return the entry point. - + @return the entry point for the background client (function) """ return codeStyleBatchCheck @@ -50,21 +50,22 @@ """ Class implementing a special report to be used with our dialog. """ + def __init__(self, options): """ Constructor - + @param options options for the report (optparse.Values) """ super().__init__(options) - + self.__repeat = options.repeat self.errors = [] - + def error_args(self, line_number, offset, code, check, *args): """ Public method to collect the error messages. - + @param line_number line number of the issue (integer) @param offset position within line of the issue (integer) @param code message code (string) @@ -72,8 +73,7 @@ @param args arguments for the message (list) @return error code (string) """ - code = super().error_args( - line_number, offset, code, check, *args) + code = super().error_args(line_number, offset, code, check, *args) if code and (self.counters[code] == 1 or self.__repeat): self.errors.append( { @@ -91,7 +91,7 @@ """ Function to extract flags starting and ending with '__' from a line comment. - + @param line line to extract flags from (string) @param startComment string identifying the start of the comment (string) @param endComment string identifying the end of a comment (string) @@ -99,29 +99,32 @@ @return list containing the extracted flags (list of strings) """ flags = [] - - if not flagsLine or ( - flagsLine and line.strip().startswith(startComment)): + + if not flagsLine or (flagsLine and line.strip().startswith(startComment)): pos = line.rfind(startComment) if pos >= 0: - comment = line[pos + len(startComment):].strip() + comment = line[pos + len(startComment) :].strip() if endComment: endPos = line.rfind(endComment) if endPos >= 0: comment = comment[:endPos] - flags = [f.strip() for f in comment.split() - if (f.startswith("__") and f.endswith("__"))] - flags += [f.strip().lower() for f in comment.split() - if f in ("noqa", "NOQA", - "nosec", "NOSEC", - "secok", "SECOK")] + flags = [ + f.strip() + for f in comment.split() + if (f.startswith("__") and f.endswith("__")) + ] + flags += [ + f.strip().lower() + for f in comment.split() + if f in ("noqa", "NOQA", "nosec", "NOSEC", "secok", "SECOK") + ] return flags def ignoreCode(code, lineFlags): """ Function to check, if the given code should be ignored as per line flags. - + @param code error code to be checked @type str @param lineFlags list of line flags to check against @@ -130,29 +133,29 @@ @rtype bool """ if lineFlags: - + if ( - "__IGNORE_WARNING__" in lineFlags or - "noqa" in lineFlags or - "nosec" in lineFlags + "__IGNORE_WARNING__" in lineFlags + or "noqa" in lineFlags + or "nosec" in lineFlags ): # ignore all warning codes return True - + for flag in lineFlags: # check individual warning code if flag.startswith("__IGNORE_WARNING_"): ignoredCode = flag[2:-2].rsplit("_", 1)[-1] if code.startswith(ignoredCode): return True - + return False def securityOk(code, lineFlags): """ Function to check, if the given code is an acknowledged security report. - + @param code error code to be checked @type str @param lineFlags list of line flags to check against @@ -162,14 +165,14 @@ """ if lineFlags: return "secok" in lineFlags - + return False def codeStyleCheck(filename, source, args): """ Do the code style check and/or fix found errors. - + @param filename source filename @type str @param source string containing the code to check @@ -194,7 +197,7 @@ def codeStyleBatchCheck(argumentsList, send, fx, cancelled, maxProcesses=0): """ Module function to check code style for a batch of files. - + @param argumentsList list of arguments tuples as given for codeStyleCheck @type list @param send reference to send function @@ -228,9 +231,8 @@ # Start worker processes workers = [ - multiprocessing.Process( - target=workerTask, args=(taskQueue, doneQueue) - ) for _ in range(NumberOfProcesses) + multiprocessing.Process(target=workerTask, args=(taskQueue, doneQueue)) + for _ in range(NumberOfProcesses) ] for worker in workers: worker.start() @@ -240,7 +242,7 @@ for i in range(len(argumentsList)): resultSent = False wasCancelled = False - + while not resultSent: try: # get result (waiting max. 3 seconds and send it to frontend @@ -252,18 +254,18 @@ if cancelled(): wasCancelled = True break - + if wasCancelled or cancelled(): # just exit the loop ignoring the results of queued tasks break - + if i < endIndex: taskQueue.put(argumentsList.pop(0)) # Tell child processes to stop for _ in range(NumberOfProcesses): - taskQueue.put('STOP') - + taskQueue.put("STOP") + for worker in workers: worker.join() worker.close() @@ -272,11 +274,11 @@ def workerTask(inputQueue, outputQueue): """ Module function acting as the parallel worker for the style check. - + @param inputQueue input queue (multiprocessing.Queue) @param outputQueue output queue (multiprocessing.Queue) """ - for filename, source, args in iter(inputQueue.get, 'STOP'): + for filename, source, args in iter(inputQueue.get, "STOP"): result = __checkCodeStyle(filename, source, args) outputQueue.put((filename, result)) @@ -284,7 +286,7 @@ def __checkSyntax(filename, source): """ Private module function to perform a syntax check. - + @param filename source filename @type str @param source string containing the code to check @@ -295,13 +297,13 @@ @rtype tuple of (dict, dict, None) or tuple of (None, None, ast.Module) """ src = "".join(source) - + try: tree = ( - ast.parse(src, filename, 'exec', type_comments=True) + ast.parse(src, filename, "exec", type_comments=True) # need the 'type_comments' parameter to include type annotations - if sys.version_info >= (3, 8) else - ast.parse(src, filename, 'exec') + if sys.version_info >= (3, 8) + else ast.parse(src, filename, "exec") ) return None, None, tree except (SyntaxError, TypeError): @@ -319,10 +321,11 @@ "offset": offset[1], "code": "E901", "args": [exc_type.__name__, exc.args[0]], - }, { + }, + { "E901": 1, }, - None + None, ) @@ -330,7 +333,7 @@ """ Private module function to perform the code style check and/or fix found errors. - + @param filename source filename @type str @param source string containing the code to check @@ -359,44 +362,69 @@ </ul> @rtype tuple of (dict, list of dict) """ - (excludeMessages, includeMessages, repeatMessages, fixCodes, noFixCodes, - fixIssues, maxLineLength, maxDocLineLength, blankLines, hangClosing, - docType, codeComplexityArgs, miscellaneousArgs, annotationArgs, - securityArgs, importsArgs, errors, eol, encoding, backup) = args - + ( + excludeMessages, + includeMessages, + repeatMessages, + fixCodes, + noFixCodes, + fixIssues, + maxLineLength, + maxDocLineLength, + blankLines, + hangClosing, + docType, + codeComplexityArgs, + miscellaneousArgs, + annotationArgs, + securityArgs, + importsArgs, + errors, + eol, + encoding, + backup, + ) = args + stats = {} if fixIssues: from CodeStyleFixer import CodeStyleFixer + fixer = CodeStyleFixer( - filename, source, fixCodes, noFixCodes, - maxLineLength, blankLines, True, eol, backup) + filename, + source, + fixCodes, + noFixCodes, + maxLineLength, + blankLines, + True, + eol, + backup, + ) # always fix in place else: fixer = None - + if not errors: if includeMessages: - select = [s.strip() for s in - includeMessages.split(',') if s.strip()] + select = [s.strip() for s in includeMessages.split(",") if s.strip()] else: select = [] if excludeMessages: - ignore = [i.strip() for i in - excludeMessages.split(',') if i.strip()] + ignore = [i.strip() for i in excludeMessages.split(",") if i.strip()] else: ignore = [] - + syntaxError, syntaxStats, tree = __checkSyntax(filename, source) - + # perform the checks only, if syntax is ok and AST tree was generated if tree: # check coding style pycodestyle.BLANK_LINES_CONFIG = { # Top level class and function. - 'top_level': blankLines[0], + "top_level": blankLines[0], # Methods and nested class and function. - 'method': blankLines[1], + "method": blankLines[1], } styleGuide = pycodestyle.StyleGuide( reporter=CodeStyleCheckerReport, @@ -410,76 +438,101 @@ report = styleGuide.check_files([filename]) stats.update(report.counters) errors = report.errors - + # check documentation style docStyleChecker = DocStyleChecker( - source, filename, select, ignore, [], repeatMessages, - maxLineLength=maxDocLineLength, docType=docType) + source, + filename, + select, + ignore, + [], + repeatMessages, + maxLineLength=maxDocLineLength, + docType=docType, + ) docStyleChecker.run() stats.update(docStyleChecker.counters) errors += docStyleChecker.errors - + # miscellaneous additional checks miscellaneousChecker = MiscellaneousChecker( - source, filename, tree, select, ignore, [], repeatMessages, - miscellaneousArgs) + source, + filename, + tree, + select, + ignore, + [], + repeatMessages, + miscellaneousArgs, + ) miscellaneousChecker.run() stats.update(miscellaneousChecker.counters) errors += miscellaneousChecker.errors - + # check code complexity complexityChecker = ComplexityChecker( - source, filename, tree, select, ignore, codeComplexityArgs) + source, filename, tree, select, ignore, codeComplexityArgs + ) complexityChecker.run() stats.update(complexityChecker.counters) errors += complexityChecker.errors - + # check function annotations if sys.version_info >= (3, 8, 0): # annotations with type comments are supported from # Python 3.8 on from Annotations.AnnotationsChecker import AnnotationsChecker + annotationsChecker = AnnotationsChecker( - source, filename, tree, select, ignore, [], repeatMessages, - annotationArgs) + source, + filename, + tree, + select, + ignore, + [], + repeatMessages, + annotationArgs, + ) annotationsChecker.run() stats.update(annotationsChecker.counters) errors += annotationsChecker.errors - + # check for security issues securityChecker = SecurityChecker( - source, filename, tree, select, ignore, [], repeatMessages, - securityArgs) + source, filename, tree, select, ignore, [], repeatMessages, securityArgs + ) securityChecker.run() stats.update(securityChecker.counters) errors += securityChecker.errors - + # check for pathlib usage pathlibChecker = PathlibChecker( - source, filename, tree, select, ignore, [], repeatMessages) + source, filename, tree, select, ignore, [], repeatMessages + ) pathlibChecker.run() stats.update(pathlibChecker.counters) errors += pathlibChecker.errors - + # check for code simplifications simplifyChecker = SimplifyChecker( - source, filename, tree, select, ignore, [], repeatMessages) + source, filename, tree, select, ignore, [], repeatMessages + ) simplifyChecker.run() stats.update(simplifyChecker.counters) errors += simplifyChecker.errors - + # check import statements importsChecker = ImportsChecker( - source, filename, tree, select, ignore, [], repeatMessages, - importsArgs) + source, filename, tree, select, ignore, [], repeatMessages, importsArgs + ) importsChecker.run() stats.update(importsChecker.counters) errors += importsChecker.errors - + elif syntaxError: errors = [syntaxError] stats.update(syntaxStats) - + errorsDict = {} for error in errors: if error["line"] > len(source): @@ -492,62 +545,72 @@ for lineno, errorsList in errorsDict.items(): errorsList.sort(key=lambda x: x[0], reverse=True) for _, error in errorsList: - error.update({ - "ignored": False, - "fixed": False, - "autofixing": False, - "fixcode": "", - "fixargs": [], - "securityOk": False, - }) - + error.update( + { + "ignored": False, + "fixed": False, + "autofixing": False, + "fixcode": "", + "fixargs": [], + "securityOk": False, + } + ) + if source: code = error["code"] lineFlags = extractLineFlags(source[lineno - 1].strip()) with contextlib.suppress(IndexError): - lineFlags += extractLineFlags(source[lineno].strip(), - flagsLine=True) - + lineFlags += extractLineFlags( + source[lineno].strip(), flagsLine=True + ) + if securityOk(code, lineFlags): error["securityOk"] = True - + if ignoreCode(code, lineFlags): error["ignored"] = True else: if fixer: res, fixcode, fixargs, id_ = fixer.fixIssue( - lineno, error["offset"], code) + lineno, error["offset"], code + ) if res == -1: deferredFixes[id_] = error else: - error.update({ - "fixed": res == 1, - "autofixing": True, - "fixcode": fixcode, - "fixargs": fixargs, - }) - + error.update( + { + "fixed": res == 1, + "autofixing": True, + "fixcode": fixcode, + "fixargs": fixargs, + } + ) + results.append(error) - + if fixer: deferredResults = fixer.finalize() for id_ in deferredResults: fixed, fixcode, fixargs = deferredResults[id_] error = deferredFixes[id_] - error.update({ - "ignored": False, - "fixed": fixed == 1, - "autofixing": True, - "fixcode": fixcode, - "fixargs": fixargs, - }) + error.update( + { + "ignored": False, + "fixed": fixed == 1, + "autofixing": True, + "fixcode": fixcode, + "fixargs": fixargs, + } + ) saveError = fixer.saveFile(encoding) if saveError: for error in results: - error.update({ - "fixcode": saveError[0], - "fixargs": saveError[1], - }) + error.update( + { + "fixcode": saveError[0], + "fixargs": saveError[1], + } + ) return stats, results