--- a/eric6/Plugins/CheckerPlugins/CodeStyleChecker/ComplexityChecker.py Mon Oct 12 19:28:42 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,255 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2015 - 2020 Detlev Offenbach <detlev@die-offenbachs.de> -# - -""" -Module implementing a checker for code complexity. -""" - -import sys -import ast - -from mccabe import PathGraphingAstVisitor - - -class ComplexityChecker(object): - """ - Class implementing a checker for code complexity. - """ - Codes = [ - "C101", - "C111", "C112", - - "C901", - ] - - def __init__(self, source, filename, select, ignore, args): - """ - Constructor - - @param source source code to be checked - @type list of str - @param filename name of the source file - @type str - @param select list of selected codes - @type list of str - @param ignore list of codes to be ignored - @type list of str - @param args dictionary of arguments for the miscellaneous checks - @type dict - """ - self.__filename = filename - self.__source = source[:] - self.__select = tuple(select) - self.__ignore = ('',) if select else tuple(ignore) - self.__args = args - - self.__defaultArgs = { - "McCabeComplexity": 10, - "LineComplexity": 15, - "LineComplexityScore": 10, - } - - # statistics counters - self.counters = {} - - # collection of detected errors - self.errors = [] - - checkersWithCodes = [ - (self.__checkMcCabeComplexity, ("C101",)), - (self.__checkLineComplexity, ("C111", "C112")), - ] - - self.__checkers = [] - for checker, codes in checkersWithCodes: - if any(not (code and self.__ignoreCode(code)) - for code in codes): - self.__checkers.append(checker) - - def __ignoreCode(self, code): - """ - Private method to check if the message code should be ignored. - - @param code message code to check for - @type str - @return flag indicating to ignore the given code - @rtype bool - """ - return (code.startswith(self.__ignore) and - not code.startswith(self.__select)) - - def __error(self, lineNumber, offset, code, *args): - """ - Private method to record an issue. - - @param lineNumber line number of the issue - @type int - @param offset position within line of the issue - @type int - @param code message code - @type str - @param args arguments for the message - @type list - """ - if self.__ignoreCode(code): - return - - if code in self.counters: - self.counters[code] += 1 - else: - self.counters[code] = 1 - - if code: - # record the issue with one based line number - self.errors.append( - { - "file": self.__filename, - "line": lineNumber, - "offset": offset, - "code": code, - "args": args, - } - ) - - def __reportInvalidSyntax(self): - """ - Private method to report a syntax error. - """ - exc_type, exc = sys.exc_info()[:2] - if len(exc.args) > 1: - offset = exc.args[1] - if len(offset) > 2: - offset = offset[1:3] - else: - offset = (1, 0) - self.__error(offset[0] - 1, offset[1] or 0, - 'C901', exc_type.__name__, exc.args[0]) - - def run(self): - """ - Public method to check the given source for code complexity. - """ - if not self.__filename or not self.__source: - # don't do anything, if essential data is missing - return - - if not self.__checkers: - # don't do anything, if no codes were selected - return - - try: - self.__tree = compile(''.join(self.__source), self.__filename, - 'exec', ast.PyCF_ONLY_AST) - except (SyntaxError, TypeError): - self.__reportInvalidSyntax() - return - - for check in self.__checkers: - check() - - def __checkMcCabeComplexity(self): - """ - Private method to check the McCabe code complexity. - """ - try: - # create the AST again because it is modified by the checker - tree = compile(''.join(self.__source), self.__filename, 'exec', - ast.PyCF_ONLY_AST) - except (SyntaxError, TypeError): - # compile errors are already reported by the run() method - return - - maxComplexity = self.__args.get("McCabeComplexity", - self.__defaultArgs["McCabeComplexity"]) - - visitor = PathGraphingAstVisitor() - visitor.preorder(tree, visitor) - for graph in visitor.graphs.values(): - if graph.complexity() > maxComplexity: - self.__error(graph.lineno, 0, "C101", - graph.entity, graph.complexity()) - - def __checkLineComplexity(self): - """ - Private method to check the complexity of a single line of code and - the median line complexity of the source code. - - Complexity is defined as the number of AST nodes produced by a line - of code. - """ - maxLineComplexity = self.__args.get( - "LineComplexity", self.__defaultArgs["LineComplexity"]) - maxLineComplexityScore = self.__args.get( - "LineComplexityScore", self.__defaultArgs["LineComplexityScore"]) - - visitor = LineComplexityVisitor() - visitor.visit(self.__tree) - - sortedItems = visitor.sortedList() - score = visitor.score() - - for line, complexity in sortedItems: - if complexity > maxLineComplexity: - self.__error(line, 0, "C111", complexity) - - if score > maxLineComplexityScore: - self.__error(0, 0, "C112", score) - - -class LineComplexityVisitor(ast.NodeVisitor): - """ - Class calculating the number of AST nodes per line of code - and the median nodes/line score. - """ - def __init__(self): - """ - Constructor - """ - super(LineComplexityVisitor, self).__init__() - self.__count = {} - - def visit(self, node): - """ - Public method to recursively visit all the nodes and add up the - instructions. - - @param node reference to the node - @type ast.AST - """ - if hasattr(node, 'lineno'): - self.__count[node.lineno] = self.__count.get(node.lineno, 0) + 1 - self.generic_visit(node) - - def sortedList(self): - """ - Public method to get a sorted list of (line, nodes) tuples. - - @return sorted list of (line, nodes) tuples - @rtype list of tuple of (int,int) - """ - lst = [(line, self.__count[line]) - for line in sorted(self.__count.keys())] - return lst - - def score(self): - """ - Public method to calculate the median. - - @return median line complexity value - @rtype float - """ - lst = self.__count.values() - sortedList = sorted(lst) - listLength = len(lst) - medianIndex = (listLength - 1) // 2 - - if listLength == 0: - return 0.0 - elif (listLength % 2): - return float(sortedList[medianIndex]) - else: - return ( - (sortedList[medianIndex] + sortedList[medianIndex + 1]) / 2.0 - )