eric7/Plugins/CheckerPlugins/CodeStyleChecker/Complexity/mccabe.py

branch
eric7
changeset 8312
800c432b34c8
parent 8218
7c09585bd960
child 8314
e3642a6a1e71
diff -r 4e8b98454baa -r 800c432b34c8 eric7/Plugins/CheckerPlugins/CodeStyleChecker/Complexity/mccabe.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Complexity/mccabe.py	Sat May 15 18:45:04 2021 +0200
@@ -0,0 +1,216 @@
+""" Meager code path measurement tool.
+    Ned Batchelder
+    http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html
+    MIT License.
+"""
+
+import collections
+import ast
+
+__version__ = '0.6.1_eric6'
+
+
+class ASTVisitor(object):
+    """Performs a depth-first walk of the AST."""
+
+    def __init__(self):
+        self.node = None
+        self._cache = {}
+
+    def default(self, node, *args):
+        for child in ast.iter_child_nodes(node):
+            self.dispatch(child, *args)
+
+    def dispatch(self, node, *args):
+        self.node = node
+        klass = node.__class__
+        meth = self._cache.get(klass)
+        if meth is None:
+            className = klass.__name__
+            meth = getattr(self.visitor, 'visit' + className, self.default)
+            self._cache[klass] = meth
+        return meth(node, *args)
+
+    def preorder(self, tree, visitor, *args):
+        """Do preorder walk of tree using visitor"""
+        self.visitor = visitor
+        visitor.visit = self.dispatch
+        self.dispatch(tree, *args)  # XXX *args make sense?
+
+
+class PathNode(object):
+    def __init__(self, name, look="circle"):
+        self.name = name
+        self.look = look
+
+    def to_dot(self):
+        print('node [shape=%s,label="%s"] %d;' % (
+            self.look, self.name, self.dot_id()))
+
+    def dot_id(self):
+        return id(self)
+
+
+class PathGraph(object):
+    def __init__(self, name, entity, lineno, column=0):
+        self.name = name
+        self.entity = entity
+        self.lineno = lineno
+        self.column = column
+        self.nodes = collections.defaultdict(list)
+
+    def connect(self, n1, n2):
+        self.nodes[n1].append(n2)
+        # Ensure that the destination node is always counted.
+        self.nodes[n2] = []
+
+    def to_dot(self):
+        print('subgraph {')
+        for node in self.nodes:
+            node.to_dot()
+        for node, nexts in self.nodes.items():
+            for next in nexts:
+                print('%s -- %s;' % (node.dot_id(), next.dot_id()))
+        print('}')
+
+    def complexity(self):
+        """ Return the McCabe complexity for the graph.
+            V-E+2
+        """
+        num_edges = sum([len(n) for n in self.nodes.values()])
+        num_nodes = len(self.nodes)
+        return num_edges - num_nodes + 2
+
+
+class PathGraphingAstVisitor(ASTVisitor):
+    """ A visitor for a parsed Abstract Syntax Tree which finds executable
+        statements.
+    """
+
+    def __init__(self):
+        super().__init__()
+        self.classname = ""
+        self.graphs = {}
+        self.reset()
+
+    def reset(self):
+        self.graph = None
+        self.tail = None
+
+    def dispatch_list(self, node_list):
+        for node in node_list:
+            self.dispatch(node)
+
+    def visitFunctionDef(self, node):
+
+        if self.classname:
+            entity = '%s%s' % (self.classname, node.name)
+        else:
+            entity = node.name
+
+        name = '%d:%d: %r' % (node.lineno, node.col_offset, entity)
+
+        if self.graph is not None:
+            # closure
+            pathnode = self.appendPathNode(name)
+            self.tail = pathnode
+            self.dispatch_list(node.body)
+            bottom = PathNode("", look='point')
+            self.graph.connect(self.tail, bottom)
+            self.graph.connect(pathnode, bottom)
+            self.tail = bottom
+        else:
+            self.graph = PathGraph(name, entity, node.lineno, node.col_offset)
+            pathnode = PathNode(name)
+            self.tail = pathnode
+            self.dispatch_list(node.body)
+            self.graphs["%s%s" % (self.classname, node.name)] = self.graph
+            self.reset()
+
+    visitAsyncFunctionDef = visitFunctionDef
+
+    def visitClassDef(self, node):
+        old_classname = self.classname
+        self.classname += node.name + "."
+        self.dispatch_list(node.body)
+        self.classname = old_classname
+
+    def appendPathNode(self, name):
+        if not self.tail:
+            return
+        pathnode = PathNode(name)
+        self.graph.connect(self.tail, pathnode)
+        self.tail = pathnode
+        return pathnode
+
+    def visitSimpleStatement(self, node):
+        if node.lineno is None:
+            lineno = 0
+        else:
+            lineno = node.lineno
+        name = "Stmt %d" % lineno
+        self.appendPathNode(name)
+
+    def default(self, node, *args):
+        if isinstance(node, ast.stmt):
+            self.visitSimpleStatement(node)
+        else:
+            super().default(node, *args)
+
+    def visitLoop(self, node):
+        name = "Loop %d" % node.lineno
+        self._subgraph(node, name)
+
+    visitAsyncFor = visitFor = visitWhile = visitLoop
+
+    def visitIf(self, node):
+        name = "If %d" % node.lineno
+        self._subgraph(node, name)
+
+    def _subgraph(self, node, name, extra_blocks=()):
+        """create the subgraphs representing any `if` and `for` statements"""
+        if self.graph is None:
+            # global loop
+            self.graph = PathGraph(name, name, node.lineno, node.col_offset)
+            pathnode = PathNode(name)
+            self._subgraph_parse(node, pathnode, extra_blocks)
+            self.graphs["%s%s" % (self.classname, name)] = self.graph
+            self.reset()
+        else:
+            pathnode = self.appendPathNode(name)
+            self._subgraph_parse(node, pathnode, extra_blocks)
+
+    def _subgraph_parse(self, node, pathnode, extra_blocks):
+        """parse the body and any `else` block of `if` and `for` statements"""
+        loose_ends = []
+        self.tail = pathnode
+        self.dispatch_list(node.body)
+        loose_ends.append(self.tail)
+        for extra in extra_blocks:
+            self.tail = pathnode
+            self.dispatch_list(extra.body)
+            loose_ends.append(self.tail)
+        if node.orelse:
+            self.tail = pathnode
+            self.dispatch_list(node.orelse)
+            loose_ends.append(self.tail)
+        else:
+            loose_ends.append(pathnode)
+        if pathnode:
+            bottom = PathNode("", look='point')
+            for le in loose_ends:
+                self.graph.connect(le, bottom)
+            self.tail = bottom
+
+    def visitTryExcept(self, node):
+        name = "TryExcept %d" % node.lineno
+        self._subgraph(node, name, extra_blocks=node.handlers)
+
+    visitTry = visitTryExcept
+
+    def visitWith(self, node):
+        name = "With %d" % node.lineno
+        self.appendPathNode(name)
+        self.dispatch_list(node.body)
+
+    visitAsyncWith = visitWith

eric ide

mercurial