]>
crepu.dev Git - config.git/blob - djavu-asus/emacs/elpy/rpc-venv/lib/python3.11/site-packages/mccabe.py
1 """ Meager code path measurement tool.
3 http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html
6 from __future__
import with_statement
12 from collections
import defaultdict
15 from ast
import iter_child_nodes
16 except ImportError: # Python 2.5
17 from flake8
.util
import ast
, iter_child_nodes
22 class ASTVisitor(object):
23 """Performs a depth-first walk of the AST."""
29 def default(self
, node
, *args
):
30 for child
in iter_child_nodes(node
):
31 self
.dispatch(child
, *args
)
33 def dispatch(self
, node
, *args
):
35 klass
= node
.__class
__
36 meth
= self
._cache
.get(klass
)
38 className
= klass
.__name
__
39 meth
= getattr(self
.visitor
, 'visit' + className
, self
.default
)
40 self
._cache
[klass
] = meth
41 return meth(node
, *args
)
43 def preorder(self
, tree
, visitor
, *args
):
44 """Do preorder walk of tree using visitor"""
45 self
.visitor
= visitor
46 visitor
.visit
= self
.dispatch
47 self
.dispatch(tree
, *args
) # XXX *args make sense?
50 class PathNode(object):
51 def __init__(self
, name
, look
="circle"):
56 print('node [shape=%s,label="%s"] %d;' % (
57 self
.look
, self
.name
, self
.dot_id()))
63 class PathGraph(object):
64 def __init__(self
, name
, entity
, lineno
, column
=0):
69 self
.nodes
= defaultdict(list)
71 def connect(self
, n1
, n2
):
72 self
.nodes
[n1
].append(n2
)
73 # Ensure that the destination node is always counted.
78 for node
in self
.nodes
:
80 for node
, nexts
in self
.nodes
.items():
82 print('%s -- %s;' % (node
.dot_id(), next
.dot_id()))
86 """ Return the McCabe complexity for the graph.
89 num_edges
= sum([len(n
) for n
in self
.nodes
.values()])
90 num_nodes
= len(self
.nodes
)
91 return num_edges
- num_nodes
+ 2
94 class PathGraphingAstVisitor(ASTVisitor
):
95 """ A visitor for a parsed Abstract Syntax Tree which finds executable
100 super(PathGraphingAstVisitor
, self
).__init
__()
109 def dispatch_list(self
, node_list
):
110 for node
in node_list
:
113 def visitFunctionDef(self
, node
):
116 entity
= '%s%s' % (self
.classname
, node
.name
)
120 name
= '%d:%d: %r' % (node
.lineno
, node
.col_offset
, entity
)
122 if self
.graph
is not None:
124 pathnode
= self
.appendPathNode(name
)
126 self
.dispatch_list(node
.body
)
127 bottom
= PathNode("", look
='point')
128 self
.graph
.connect(self
.tail
, bottom
)
129 self
.graph
.connect(pathnode
, bottom
)
132 self
.graph
= PathGraph(name
, entity
, node
.lineno
, node
.col_offset
)
133 pathnode
= PathNode(name
)
135 self
.dispatch_list(node
.body
)
136 self
.graphs
["%s%s" % (self
.classname
, node
.name
)] = self
.graph
139 visitAsyncFunctionDef
= visitFunctionDef
141 def visitClassDef(self
, node
):
142 old_classname
= self
.classname
143 self
.classname
+= node
.name
+ "."
144 self
.dispatch_list(node
.body
)
145 self
.classname
= old_classname
147 def appendPathNode(self
, name
):
150 pathnode
= PathNode(name
)
151 self
.graph
.connect(self
.tail
, pathnode
)
155 def visitSimpleStatement(self
, node
):
156 if node
.lineno
is None:
160 name
= "Stmt %d" % lineno
161 self
.appendPathNode(name
)
163 def default(self
, node
, *args
):
164 if isinstance(node
, ast
.stmt
):
165 self
.visitSimpleStatement(node
)
167 super(PathGraphingAstVisitor
, self
).default(node
, *args
)
169 def visitLoop(self
, node
):
170 name
= "Loop %d" % node
.lineno
171 self
._subgraph
(node
, name
)
173 visitAsyncFor
= visitFor
= visitWhile
= visitLoop
175 def visitIf(self
, node
):
176 name
= "If %d" % node
.lineno
177 self
._subgraph
(node
, name
)
179 def _subgraph(self
, node
, name
, extra_blocks
=()):
180 """create the subgraphs representing any `if` and `for` statements"""
181 if self
.graph
is None:
183 self
.graph
= PathGraph(name
, name
, node
.lineno
, node
.col_offset
)
184 pathnode
= PathNode(name
)
185 self
._subgraph
_parse
(node
, pathnode
, extra_blocks
)
186 self
.graphs
["%s%s" % (self
.classname
, name
)] = self
.graph
189 pathnode
= self
.appendPathNode(name
)
190 self
._subgraph
_parse
(node
, pathnode
, extra_blocks
)
192 def _subgraph_parse(self
, node
, pathnode
, extra_blocks
):
193 """parse the body and any `else` block of `if` and `for` statements"""
196 self
.dispatch_list(node
.body
)
197 loose_ends
.append(self
.tail
)
198 for extra
in extra_blocks
:
200 self
.dispatch_list(extra
.body
)
201 loose_ends
.append(self
.tail
)
204 self
.dispatch_list(node
.orelse
)
205 loose_ends
.append(self
.tail
)
207 loose_ends
.append(pathnode
)
209 bottom
= PathNode("", look
='point')
210 for le
in loose_ends
:
211 self
.graph
.connect(le
, bottom
)
214 def visitTryExcept(self
, node
):
215 name
= "TryExcept %d" % node
.lineno
216 self
._subgraph
(node
, name
, extra_blocks
=node
.handlers
)
218 visitTry
= visitTryExcept
220 def visitWith(self
, node
):
221 name
= "With %d" % node
.lineno
222 self
.appendPathNode(name
)
223 self
.dispatch_list(node
.body
)
225 visitAsyncWith
= visitWith
228 class McCabeChecker(object):
229 """McCabe cyclomatic complexity checker."""
231 version
= __version__
233 _error_tmpl
= "C901 %r is too complex (%d)"
236 def __init__(self
, tree
, filename
):
240 def add_options(cls
, parser
):
241 flag
= '--max-complexity'
246 'help': 'McCabe complexity threshold',
247 'parse_from_config': 'True',
249 config_opts
= getattr(parser
, 'config_options', None)
250 if isinstance(config_opts
, list):
252 kwargs
.pop('parse_from_config')
253 parser
.add_option(flag
, **kwargs
)
254 parser
.config_options
.append('max-complexity')
256 parser
.add_option(flag
, **kwargs
)
259 def parse_options(cls
, options
):
260 cls
.max_complexity
= int(options
.max_complexity
)
263 if self
.max_complexity
< 0:
265 visitor
= PathGraphingAstVisitor()
266 visitor
.preorder(self
.tree
, visitor
)
267 for graph
in visitor
.graphs
.values():
268 if graph
.complexity() > self
.max_complexity
:
269 text
= self
._error
_tmpl
% (graph
.entity
, graph
.complexity())
270 yield graph
.lineno
, graph
.column
, text
, type(self
)
273 def get_code_complexity(code
, threshold
=7, filename
='stdin'):
275 tree
= compile(code
, filename
, "exec", ast
.PyCF_ONLY_AST
)
277 e
= sys
.exc_info()[1]
278 sys
.stderr
.write("Unable to parse %s: %s\n" % (filename
, e
))
282 McCabeChecker
.max_complexity
= threshold
283 for lineno
, offset
, text
, check
in McCabeChecker(tree
, filename
).run():
284 complx
.append('%s:%d:1: %s' % (filename
, lineno
, text
))
288 print('\n'.join(complx
))
292 def get_module_complexity(module_path
, threshold
=7):
293 """Returns the complexity of a module"""
294 code
= _read(module_path
)
295 return get_code_complexity(code
, threshold
, filename
=module_path
)
299 if (2, 5) < sys
.version_info
< (3, 0):
300 with
open(filename
, 'rU') as f
:
302 elif (3, 0) <= sys
.version_info
< (4, 0):
303 """Read the source code."""
305 with
open(filename
, 'rb') as f
:
306 (encoding
, _
) = tokenize
.detect_encoding(f
.readline
)
307 except (LookupError, SyntaxError, UnicodeError):
308 # Fall back if file encoding is improperly declared
309 with
open(filename
, encoding
='latin-1') as f
:
311 with
open(filename
, 'r', encoding
=encoding
) as f
:
318 opar
= optparse
.OptionParser()
319 opar
.add_option("-d", "--dot", dest
="dot",
320 help="output a graphviz dot file", action
="store_true")
321 opar
.add_option("-m", "--min", dest
="threshold",
322 help="minimum complexity for output", type="int",
325 options
, args
= opar
.parse_args(argv
)
327 code
= _read(args
[0])
328 tree
= compile(code
, args
[0], "exec", ast
.PyCF_ONLY_AST
)
329 visitor
= PathGraphingAstVisitor()
330 visitor
.preorder(tree
, visitor
)
334 for graph
in visitor
.graphs
.values():
335 if (not options
.threshold
or
336 graph
.complexity() >= options
.threshold
):
340 for graph
in visitor
.graphs
.values():
341 if graph
.complexity() >= options
.threshold
:
342 print(graph
.name
, graph
.complexity())
345 if __name__
== '__main__':