eric6/Plugins/CheckerPlugins/CodeStyleChecker/mccabe.py

changeset 6942
2602857055c5
parent 5620
6bca68319bb4
child 7639
422fd05e9c91
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 """ Meager code path measurement tool.
2 Ned Batchelder
3 http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html
4 MIT License.
5 """
6 from __future__ import with_statement
7
8 import collections
9 import ast
10
11 __version__ = '0.6.1_eric6'
12
13
14 class ASTVisitor(object):
15 """Performs a depth-first walk of the AST."""
16
17 def __init__(self):
18 self.node = None
19 self._cache = {}
20
21 def default(self, node, *args):
22 for child in ast.iter_child_nodes(node):
23 self.dispatch(child, *args)
24
25 def dispatch(self, node, *args):
26 self.node = node
27 klass = node.__class__
28 meth = self._cache.get(klass)
29 if meth is None:
30 className = klass.__name__
31 meth = getattr(self.visitor, 'visit' + className, self.default)
32 self._cache[klass] = meth
33 return meth(node, *args)
34
35 def preorder(self, tree, visitor, *args):
36 """Do preorder walk of tree using visitor"""
37 self.visitor = visitor
38 visitor.visit = self.dispatch
39 self.dispatch(tree, *args) # XXX *args make sense?
40
41
42 class PathNode(object):
43 def __init__(self, name, look="circle"):
44 self.name = name
45 self.look = look
46
47 def to_dot(self):
48 print('node [shape=%s,label="%s"] %d;' % (
49 self.look, self.name, self.dot_id()))
50
51 def dot_id(self):
52 return id(self)
53
54
55 class PathGraph(object):
56 def __init__(self, name, entity, lineno, column=0):
57 self.name = name
58 self.entity = entity
59 self.lineno = lineno
60 self.column = column
61 self.nodes = collections.defaultdict(list)
62
63 def connect(self, n1, n2):
64 self.nodes[n1].append(n2)
65 # Ensure that the destination node is always counted.
66 self.nodes[n2] = []
67
68 def to_dot(self):
69 print('subgraph {')
70 for node in self.nodes:
71 node.to_dot()
72 for node, nexts in self.nodes.items():
73 for next in nexts:
74 print('%s -- %s;' % (node.dot_id(), next.dot_id()))
75 print('}')
76
77 def complexity(self):
78 """ Return the McCabe complexity for the graph.
79 V-E+2
80 """
81 num_edges = sum([len(n) for n in self.nodes.values()])
82 num_nodes = len(self.nodes)
83 return num_edges - num_nodes + 2
84
85
86 class PathGraphingAstVisitor(ASTVisitor):
87 """ A visitor for a parsed Abstract Syntax Tree which finds executable
88 statements.
89 """
90
91 def __init__(self):
92 super(PathGraphingAstVisitor, self).__init__()
93 self.classname = ""
94 self.graphs = {}
95 self.reset()
96
97 def reset(self):
98 self.graph = None
99 self.tail = None
100
101 def dispatch_list(self, node_list):
102 for node in node_list:
103 self.dispatch(node)
104
105 def visitFunctionDef(self, node):
106
107 if self.classname:
108 entity = '%s%s' % (self.classname, node.name)
109 else:
110 entity = node.name
111
112 name = '%d:%d: %r' % (node.lineno, node.col_offset, entity)
113
114 if self.graph is not None:
115 # closure
116 pathnode = self.appendPathNode(name)
117 self.tail = pathnode
118 self.dispatch_list(node.body)
119 bottom = PathNode("", look='point')
120 self.graph.connect(self.tail, bottom)
121 self.graph.connect(pathnode, bottom)
122 self.tail = bottom
123 else:
124 self.graph = PathGraph(name, entity, node.lineno, node.col_offset)
125 pathnode = PathNode(name)
126 self.tail = pathnode
127 self.dispatch_list(node.body)
128 self.graphs["%s%s" % (self.classname, node.name)] = self.graph
129 self.reset()
130
131 visitAsyncFunctionDef = visitFunctionDef
132
133 def visitClassDef(self, node):
134 old_classname = self.classname
135 self.classname += node.name + "."
136 self.dispatch_list(node.body)
137 self.classname = old_classname
138
139 def appendPathNode(self, name):
140 if not self.tail:
141 return
142 pathnode = PathNode(name)
143 self.graph.connect(self.tail, pathnode)
144 self.tail = pathnode
145 return pathnode
146
147 def visitSimpleStatement(self, node):
148 if node.lineno is None:
149 lineno = 0
150 else:
151 lineno = node.lineno
152 name = "Stmt %d" % lineno
153 self.appendPathNode(name)
154
155 def default(self, node, *args):
156 if isinstance(node, ast.stmt):
157 self.visitSimpleStatement(node)
158 else:
159 super(PathGraphingAstVisitor, self).default(node, *args)
160
161 def visitLoop(self, node):
162 name = "Loop %d" % node.lineno
163 self._subgraph(node, name)
164
165 visitAsyncFor = visitFor = visitWhile = visitLoop
166
167 def visitIf(self, node):
168 name = "If %d" % node.lineno
169 self._subgraph(node, name)
170
171 def _subgraph(self, node, name, extra_blocks=()):
172 """create the subgraphs representing any `if` and `for` statements"""
173 if self.graph is None:
174 # global loop
175 self.graph = PathGraph(name, name, node.lineno, node.col_offset)
176 pathnode = PathNode(name)
177 self._subgraph_parse(node, pathnode, extra_blocks)
178 self.graphs["%s%s" % (self.classname, name)] = self.graph
179 self.reset()
180 else:
181 pathnode = self.appendPathNode(name)
182 self._subgraph_parse(node, pathnode, extra_blocks)
183
184 def _subgraph_parse(self, node, pathnode, extra_blocks):
185 """parse the body and any `else` block of `if` and `for` statements"""
186 loose_ends = []
187 self.tail = pathnode
188 self.dispatch_list(node.body)
189 loose_ends.append(self.tail)
190 for extra in extra_blocks:
191 self.tail = pathnode
192 self.dispatch_list(extra.body)
193 loose_ends.append(self.tail)
194 if node.orelse:
195 self.tail = pathnode
196 self.dispatch_list(node.orelse)
197 loose_ends.append(self.tail)
198 else:
199 loose_ends.append(pathnode)
200 if pathnode:
201 bottom = PathNode("", look='point')
202 for le in loose_ends:
203 self.graph.connect(le, bottom)
204 self.tail = bottom
205
206 def visitTryExcept(self, node):
207 name = "TryExcept %d" % node.lineno
208 self._subgraph(node, name, extra_blocks=node.handlers)
209
210 visitTry = visitTryExcept
211
212 def visitWith(self, node):
213 name = "With %d" % node.lineno
214 self.appendPathNode(name)
215 self.dispatch_list(node.body)
216
217 visitAsyncWith = visitWith
218
219 #
220 # eflag: noqa = M702

eric ide

mercurial