From a593dd01a462eae39ec5786f13ff2c97622ac581 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Thu, 21 Feb 2013 00:00:54 +0100 Subject: [PATCH] Install mccabe as a dependency --- flake8/mccabe.py | 310 ------------------------------------ flake8/tests/test_mccabe.py | 42 ----- setup.py | 2 +- 3 files changed, 1 insertion(+), 353 deletions(-) delete mode 100644 flake8/mccabe.py delete mode 100644 flake8/tests/test_mccabe.py diff --git a/flake8/mccabe.py b/flake8/mccabe.py deleted file mode 100644 index 8b7ac4b..0000000 --- a/flake8/mccabe.py +++ /dev/null @@ -1,310 +0,0 @@ -""" Meager code path measurement tool. - Ned Batchelder - http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html - MIT License. -""" -from __future__ import with_statement - -import optparse -import sys -from collections import defaultdict - -from flake8.util import ast, iter_child_nodes - -version = 0.1 - - -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 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): - self.name = name - self.entity = entity - self.lineno = lineno - self.nodes = defaultdict(list) - - def connect(self, n1, n2): - self.nodes[n1].append(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(PathGraphingAstVisitor, self).__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:1: %r' % (node.lineno, 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) - pathnode = PathNode(name) - self.tail = pathnode - self.dispatch_list(node.body) - self.graphs["%s%s" % (self.classname, node.name)] = self.graph - self.reset() - - 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) - - visitAssert = visitAssign = visitAugAssign = visitDelete = visitPrint = \ - visitRaise = visitYield = visitImport = visitCall = visitSubscript = \ - visitPass = visitContinue = visitBreak = visitGlobal = visitReturn = \ - visitSimpleStatement - - def visitLoop(self, node): - name = "Loop %d" % node.lineno - - if self.graph is None: - # global loop - self.graph = PathGraph(name, name, node.lineno) - pathnode = PathNode(name) - self.tail = pathnode - self.dispatch_list(node.body) - self.graphs["%s%s" % (self.classname, name)] = self.graph - self.reset() - else: - 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 - - # TODO: else clause in node.orelse - - visitFor = visitWhile = visitLoop - - def visitIf(self, node): - name = "If %d" % node.lineno - pathnode = self.appendPathNode(name) - loose_ends = [] - self.dispatch_list(node.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 - pathnode = self.appendPathNode(name) - loose_ends = [] - self.dispatch_list(node.body) - loose_ends.append(self.tail) - for handler in node.handlers: - self.tail = pathnode - self.dispatch_list(handler.body) - loose_ends.append(self.tail) - if pathnode: - bottom = PathNode("", look='point') - for le in loose_ends: - self.graph.connect(le, bottom) - self.tail = bottom - - def visitWith(self, node): - name = "With %d" % node.lineno - self.appendPathNode(name) - self.dispatch_list(node.body) - - -class McCabeChecker(object): - """McCabe cyclomatic complexity checker.""" - name = 'mccabe' - version = version - _code = 'C901' - _error_tmpl = "C901 %r is too complex (%d)" - max_complexity = 0 - - def __init__(self, tree, filename): - self.tree = tree - - @classmethod - def add_options(cls, parser): - parser.add_option('--max-complexity', default=-1, action='store', - type='int', help="McCabe complexity threshold") - parser.config_options.append('max-complexity') - - @classmethod - def parse_options(cls, options): - cls.max_complexity = options.max_complexity - - def run(self): - if self.max_complexity < 0: - return - visitor = PathGraphingAstVisitor() - visitor.preorder(self.tree, visitor) - for graph in visitor.graphs.values(): - graph_complexity = graph.complexity() - if graph_complexity >= self.max_complexity: - text = self._error_tmpl % (graph.entity, graph_complexity) - yield graph.lineno, 0, text, type(self) - - -def get_code_complexity(code, min_complexity=7, filename='stdin'): - try: - tree = compile(code, filename, "exec", ast.PyCF_ONLY_AST) - except SyntaxError: - e = sys.exc_info()[1] - sys.stderr.write("Unable to parse %s: %s\n" % (filename, e)) - return 0 - - complx = [] - McCabeChecker.max_complexity = min_complexity - for lineno, offset, text, check in McCabeChecker(tree, filename).run(): - complx.append('%s:%d:1: %s' % (filename, lineno, text)) - - if len(complx) == 0: - return 0 - print('\n'.join(complx)) - return len(complx) - - -def get_module_complexity(module_path, min_complexity=7): - """Returns the complexity of a module""" - with open(module_path, "rU") as mod: - code = mod.read() - return get_code_complexity(code, min_complexity, filename=module_path) - - -def main(argv): - opar = optparse.OptionParser() - opar.add_option("-d", "--dot", dest="dot", - help="output a graphviz dot file", action="store_true") - opar.add_option("-m", "--min", dest="min", - help="minimum complexity for output", type="int", - default=2) - - options, args = opar.parse_args(argv) - - with open(args[0], "rU") as mod: - code = mod.read() - tree = compile(code, args[0], "exec", ast.PyCF_ONLY_AST) - visitor = PathGraphingAstVisitor() - visitor.preorder(tree, visitor) - - if options.dot: - print('graph {') - for graph in visitor.graphs.values(): - if graph.complexity() >= options.min: - graph.to_dot() - print('}') - else: - for graph in visitor.graphs.values(): - if graph.complexity() >= options.min: - print(graph.name, graph.complexity()) - - -if __name__ == '__main__': - main(sys.argv[1:]) diff --git a/flake8/tests/test_mccabe.py b/flake8/tests/test_mccabe.py deleted file mode 100644 index a220c83..0000000 --- a/flake8/tests/test_mccabe.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -import unittest -import sys -try: - from StringIO import StringIO -except ImportError: - from io import StringIO # NOQA - -from flake8.mccabe import get_code_complexity - - -_GLOBAL = """\ - -for i in range(10): - pass - -def a(): - def b(): - def c(): - pass - c() - b() - -""" - - -class McCabeTest(unittest.TestCase): - - def setUp(self): - self.old = sys.stdout - self.out = sys.stdout = StringIO() - - def tearDown(self): - sys.sdtout = self.old - - def test_sample(self): - self.assertEqual(get_code_complexity(_GLOBAL, 1), 2) - self.out.seek(0) - res = self.out.read().strip().split('\n') - wanted = ["stdin:5:1: C901 'a' is too complex (4)", - "stdin:2:1: C901 'Loop 2' is too complex (2)"] - self.assertEqual(res, wanted) diff --git a/setup.py b/setup.py index 70693e8..803c132 100644 --- a/setup.py +++ b/setup.py @@ -34,13 +34,13 @@ setup( "setuptools", "pyflakes >= 0.6.1", "pep8 >= 1.4.2", + "mccabe >= 0.2a0", ], entry_points={ 'distutils.commands': ['flake8 = flake8.main:Flake8Command'], 'console_scripts': ['flake8 = flake8.main:main'], 'flake8.extension': [ 'F = flake8._pyflakes:FlakesChecker', - 'C90 = flake8.mccabe:McCabeChecker', ], }, classifiers=[