Merge branch flint-merge-mccabe-plugin

This commit is contained in:
Ian Cordasco 2013-02-21 19:47:44 -05:00
commit b2b4590ba6
5 changed files with 2 additions and 426 deletions

72
LICENSE
View file

@ -19,75 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
== McCabe License (MIT) ==
Copyright (C) <year> Ned Batchelder
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
== PyFlakes license (MIT) ==
Copyright (c) 2005 Divmod, Inc., http://www.divmod.com/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
== pep8 license (expat) ==
Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,4 +1,4 @@
include *.rst
include CONTRIBUTORS.txt
include README
include LICENSE
recursive-include scripts flake8.*

View file

@ -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:])

View file

@ -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)

View file

@ -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=[