mirror of
https://github.com/PyCQA/flake8.git
synced 2026-04-11 23:34:17 +00:00
merged branch python3
This commit is contained in:
commit
29bb206fd4
9 changed files with 153 additions and 48 deletions
|
|
@ -1,3 +1,8 @@
|
||||||
lib
|
lib
|
||||||
include
|
include
|
||||||
.*\.pyc$
|
.*\.pyc$
|
||||||
|
dist
|
||||||
|
bin
|
||||||
|
flake8.egg-info
|
||||||
|
man
|
||||||
|
\.Python
|
||||||
|
|
|
||||||
100
flake8/mccabe.py
100
flake8/mccabe.py
|
|
@ -3,9 +3,50 @@
|
||||||
http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html
|
http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html
|
||||||
MIT License.
|
MIT License.
|
||||||
"""
|
"""
|
||||||
import compiler
|
try:
|
||||||
|
from compiler import parse
|
||||||
|
iter_child_nodes = None
|
||||||
|
except ImportError:
|
||||||
|
from ast import parse, iter_child_nodes
|
||||||
|
|
||||||
import optparse
|
import optparse
|
||||||
import sys
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
||||||
|
class ASTVisitor:
|
||||||
|
|
||||||
|
VERBOSE = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.node = None
|
||||||
|
self._cache = {}
|
||||||
|
|
||||||
|
def default(self, node, *args):
|
||||||
|
if hasattr(node, 'getChildNodes'):
|
||||||
|
children = node.getChildNodes()
|
||||||
|
else:
|
||||||
|
children = iter_child_nodes(node)
|
||||||
|
|
||||||
|
for child in children:
|
||||||
|
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:
|
class PathNode:
|
||||||
|
|
@ -14,8 +55,8 @@ class PathNode:
|
||||||
self.look = look
|
self.look = look
|
||||||
|
|
||||||
def to_dot(self):
|
def to_dot(self):
|
||||||
print 'node [shape=%s,label="%s"] %d;' % \
|
print('node [shape=%s,label="%s"] %d;' % \
|
||||||
(self.look, self.name, self.dot_id())
|
(self.look, self.name, self.dot_id()))
|
||||||
|
|
||||||
def dot_id(self):
|
def dot_id(self):
|
||||||
return id(self)
|
return id(self)
|
||||||
|
|
@ -24,25 +65,19 @@ class PathNode:
|
||||||
class PathGraph:
|
class PathGraph:
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.nodes = {}
|
self.nodes = defaultdict(list)
|
||||||
|
|
||||||
def add_node(self, n):
|
|
||||||
assert n
|
|
||||||
self.nodes.setdefault(n, [])
|
|
||||||
|
|
||||||
def connect(self, n1, n2):
|
def connect(self, n1, n2):
|
||||||
assert n1
|
self.nodes[n1].append(n2)
|
||||||
assert n2
|
|
||||||
self.nodes.setdefault(n1, []).append(n2)
|
|
||||||
|
|
||||||
def to_dot(self):
|
def to_dot(self):
|
||||||
print 'subgraph {'
|
print('subgraph {')
|
||||||
for node in self.nodes:
|
for node in self.nodes:
|
||||||
node.to_dot()
|
node.to_dot()
|
||||||
for node, nexts in self.nodes.items():
|
for node, nexts in self.nodes.items():
|
||||||
for next in nexts:
|
for next in nexts:
|
||||||
print '%s -- %s;' % (node.dot_id(), next.dot_id())
|
print('%s -- %s;' % (node.dot_id(), next.dot_id()))
|
||||||
print '}'
|
print('}')
|
||||||
|
|
||||||
def complexity(self):
|
def complexity(self):
|
||||||
""" Return the McCabe complexity for the graph.
|
""" Return the McCabe complexity for the graph.
|
||||||
|
|
@ -53,13 +88,13 @@ class PathGraph:
|
||||||
return num_edges - num_nodes + 2
|
return num_edges - num_nodes + 2
|
||||||
|
|
||||||
|
|
||||||
class PathGraphingAstVisitor(compiler.visitor.ASTVisitor):
|
class PathGraphingAstVisitor(ASTVisitor):
|
||||||
""" A visitor for a parsed Abstract Syntax Tree which finds executable
|
""" A visitor for a parsed Abstract Syntax Tree which finds executable
|
||||||
statements.
|
statements.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
compiler.visitor.ASTVisitor.__init__(self)
|
ASTVisitor.__init__(self)
|
||||||
self.classname = ""
|
self.classname = ""
|
||||||
self.graphs = {}
|
self.graphs = {}
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
@ -94,6 +129,8 @@ class PathGraphingAstVisitor(compiler.visitor.ASTVisitor):
|
||||||
self.graphs["%s%s" % (self.classname, node.name)] = self.graph
|
self.graphs["%s%s" % (self.classname, node.name)] = self.graph
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
|
visitFunctionDef = visitFunction
|
||||||
|
|
||||||
def visitClass(self, node):
|
def visitClass(self, node):
|
||||||
old_classname = self.classname
|
old_classname = self.classname
|
||||||
self.classname += node.name + "."
|
self.classname += node.name + "."
|
||||||
|
|
@ -104,7 +141,6 @@ class PathGraphingAstVisitor(compiler.visitor.ASTVisitor):
|
||||||
if not self.tail:
|
if not self.tail:
|
||||||
return
|
return
|
||||||
pathnode = PathNode(name)
|
pathnode = PathNode(name)
|
||||||
self.graph.add_node(pathnode)
|
|
||||||
self.graph.connect(self.tail, pathnode)
|
self.graph.connect(self.tail, pathnode)
|
||||||
self.tail = pathnode
|
self.tail = pathnode
|
||||||
return pathnode
|
return pathnode
|
||||||
|
|
@ -171,13 +207,29 @@ class PathGraphingAstVisitor(compiler.visitor.ASTVisitor):
|
||||||
# TODO: visitTryFinally
|
# TODO: visitTryFinally
|
||||||
# TODO: visitWith
|
# TODO: visitWith
|
||||||
|
|
||||||
|
# XXX todo: determine which ones can add to the complexity
|
||||||
|
# py2
|
||||||
|
# TODO: visitStmt
|
||||||
|
# TODO: visitAssName
|
||||||
|
# TODO: visitCallFunc
|
||||||
|
# TODO: visitConst
|
||||||
|
|
||||||
|
# py3
|
||||||
|
# TODO: visitStore
|
||||||
|
# TODO: visitCall
|
||||||
|
# TODO: visitLoad
|
||||||
|
# TODO: visitNum
|
||||||
|
# TODO: visitarguments
|
||||||
|
# TODO: visitExpr
|
||||||
|
|
||||||
|
|
||||||
def get_code_complexity(code, min=7, filename='stdin'):
|
def get_code_complexity(code, min=7, filename='stdin'):
|
||||||
complex = []
|
complex = []
|
||||||
try:
|
try:
|
||||||
ast = compiler.parse(code)
|
ast = parse(code)
|
||||||
except AttributeError as e:
|
except AttributeError:
|
||||||
print >> sys.stderr, "Unable to parse %s: %s" % (filename, e)
|
e = sys.exc_info()[1]
|
||||||
|
sys.stderr.write("Unable to parse %s: %s\n" % (filename, e))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
visitor = PathGraphingAstVisitor()
|
visitor = PathGraphingAstVisitor()
|
||||||
|
|
@ -215,20 +267,20 @@ def main(argv):
|
||||||
options, args = opar.parse_args(argv)
|
options, args = opar.parse_args(argv)
|
||||||
|
|
||||||
text = open(args[0], "rU").read() + '\n\n'
|
text = open(args[0], "rU").read() + '\n\n'
|
||||||
ast = compiler.parse(text)
|
ast = parse(text)
|
||||||
visitor = PathGraphingAstVisitor()
|
visitor = PathGraphingAstVisitor()
|
||||||
visitor.preorder(ast, visitor)
|
visitor.preorder(ast, visitor)
|
||||||
|
|
||||||
if options.dot:
|
if options.dot:
|
||||||
print 'graph {'
|
print('graph {')
|
||||||
for graph in visitor.graphs.values():
|
for graph in visitor.graphs.values():
|
||||||
if graph.complexity() >= options.min:
|
if graph.complexity() >= options.min:
|
||||||
graph.to_dot()
|
graph.to_dot()
|
||||||
print '}'
|
print('}')
|
||||||
else:
|
else:
|
||||||
for graph in visitor.graphs.values():
|
for graph in visitor.graphs.values():
|
||||||
if graph.complexity() >= options.min:
|
if graph.complexity() >= options.min:
|
||||||
print graph.name, graph.complexity()
|
print(graph.name, graph.complexity())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ for space.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from flake8 import __version__ as flake8_version
|
from flake8 import __version__ as flake8_version
|
||||||
from pyflakes import __version__ as pep8_version
|
from flake8.pyflakes import __version__ as pep8_version
|
||||||
|
|
||||||
__version__ = '0.6.1'
|
__version__ = '0.6.1'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,11 @@
|
||||||
# (c) 2005-2010 Divmod, Inc.
|
# (c) 2005-2010 Divmod, Inc.
|
||||||
# See LICENSE file for details
|
# See LICENSE file for details
|
||||||
|
|
||||||
import __builtin__
|
try:
|
||||||
|
import __builtin__
|
||||||
|
except ImportError:
|
||||||
|
import builtins as __builtin__
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import _ast
|
import _ast
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -251,7 +255,7 @@ class Checker(object):
|
||||||
all = []
|
all = []
|
||||||
|
|
||||||
# Look for imported names that aren't used.
|
# Look for imported names that aren't used.
|
||||||
for importation in scope.itervalues():
|
for importation in scope.values():
|
||||||
if isinstance(importation, Importation):
|
if isinstance(importation, Importation):
|
||||||
if not importation.used and importation.name not in all:
|
if not importation.used and importation.name not in all:
|
||||||
self.report(
|
self.report(
|
||||||
|
|
@ -284,7 +288,7 @@ class Checker(object):
|
||||||
def handleNode(self, node, parent):
|
def handleNode(self, node, parent):
|
||||||
node.parent = parent
|
node.parent = parent
|
||||||
if self.traceTree:
|
if self.traceTree:
|
||||||
print ' ' * self.nodeDepth + node.__class__.__name__
|
print(' ' * self.nodeDepth + node.__class__.__name__)
|
||||||
self.nodeDepth += 1
|
self.nodeDepth += 1
|
||||||
if self.futuresAllowed and not \
|
if self.futuresAllowed and not \
|
||||||
(isinstance(node, _ast.ImportFrom) or self.isDocstring(node)):
|
(isinstance(node, _ast.ImportFrom) or self.isDocstring(node)):
|
||||||
|
|
@ -296,7 +300,7 @@ class Checker(object):
|
||||||
finally:
|
finally:
|
||||||
self.nodeDepth -= 1
|
self.nodeDepth -= 1
|
||||||
if self.traceTree:
|
if self.traceTree:
|
||||||
print ' ' * self.nodeDepth + 'end ' + node.__class__.__name__
|
print(' ' * self.nodeDepth + 'end ' + node.__class__.__name__)
|
||||||
|
|
||||||
def ignore(self, node):
|
def ignore(self, node):
|
||||||
pass
|
pass
|
||||||
|
|
@ -325,7 +329,10 @@ class Checker(object):
|
||||||
EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore
|
EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore
|
||||||
|
|
||||||
# additional node types
|
# additional node types
|
||||||
COMPREHENSION = EXCEPTHANDLER = KEYWORD = handleChildren
|
COMPREHENSION = KEYWORD = handleChildren
|
||||||
|
|
||||||
|
def EXCEPTHANDLER(self, node):
|
||||||
|
self.scope[node.name] = node
|
||||||
|
|
||||||
def addBinding(self, lineno, value, reportRedef=True):
|
def addBinding(self, lineno, value, reportRedef=True):
|
||||||
'''Called when a binding is altered.
|
'''Called when a binding is altered.
|
||||||
|
|
@ -527,10 +534,15 @@ class Checker(object):
|
||||||
if isinstance(arg, _ast.Tuple):
|
if isinstance(arg, _ast.Tuple):
|
||||||
addArgs(arg.elts)
|
addArgs(arg.elts)
|
||||||
else:
|
else:
|
||||||
if arg.id in args:
|
try:
|
||||||
|
id_ = arg.id
|
||||||
|
except AttributeError:
|
||||||
|
id_ = arg.arg
|
||||||
|
|
||||||
|
if id_ in args:
|
||||||
self.report(messages.DuplicateArgument,
|
self.report(messages.DuplicateArgument,
|
||||||
node.lineno, arg.id)
|
node.lineno, id_)
|
||||||
args.append(arg.id)
|
args.append(id_)
|
||||||
|
|
||||||
self.pushFunctionScope()
|
self.pushFunctionScope()
|
||||||
addArgs(node.args.args)
|
addArgs(node.args.args)
|
||||||
|
|
@ -554,7 +566,7 @@ class Checker(object):
|
||||||
"""
|
"""
|
||||||
Check to see if any assignments have not been used.
|
Check to see if any assignments have not been used.
|
||||||
"""
|
"""
|
||||||
for name, binding in self.scope.iteritems():
|
for name, binding in self.scope.items():
|
||||||
if (not binding.used and not name in self.scope.globals
|
if (not binding.used and not name in self.scope.globals
|
||||||
and isinstance(binding, Assignment)):
|
and isinstance(binding, Assignment)):
|
||||||
self.report(messages.UnusedVariable,
|
self.report(messages.UnusedVariable,
|
||||||
|
|
@ -629,13 +641,14 @@ def checkPath(filename):
|
||||||
@return: the number of warnings printed
|
@return: the number of warnings printed
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return check(file(filename, 'U').read() + '\n', filename)
|
return check(open(filename, 'U').read() + '\n', filename)
|
||||||
except IOError, msg:
|
except IOError:
|
||||||
|
msg = sys.exc_info()[1]
|
||||||
print >> sys.stderr, "%s: %s" % (filename, msg.args[1])
|
print >> sys.stderr, "%s: %s" % (filename, msg.args[1])
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
def check(codeString, filename):
|
def check(codeString, filename='(code)'):
|
||||||
"""
|
"""
|
||||||
Check the Python source given by C{codeString} for flakes.
|
Check the Python source given by C{codeString} for flakes.
|
||||||
|
|
||||||
|
|
@ -652,7 +665,8 @@ def check(codeString, filename):
|
||||||
# First, compile into an AST and handle syntax errors.
|
# First, compile into an AST and handle syntax errors.
|
||||||
try:
|
try:
|
||||||
tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST)
|
tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST)
|
||||||
except SyntaxError, value:
|
except SyntaxError:
|
||||||
|
value = sys.exc_info()[1]
|
||||||
msg = value.args[0]
|
msg = value.args[0]
|
||||||
|
|
||||||
(lineno, offset, text) = value.lineno, value.offset, value.text
|
(lineno, offset, text) = value.lineno, value.offset, value.text
|
||||||
|
|
@ -679,13 +693,15 @@ def check(codeString, filename):
|
||||||
else:
|
else:
|
||||||
# Okay, it's syntactically valid. Now check it.
|
# Okay, it's syntactically valid. Now check it.
|
||||||
w = Checker(tree, filename)
|
w = Checker(tree, filename)
|
||||||
w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
|
sorting = [(msg.lineno, msg) for msg in w.messages]
|
||||||
|
sorting.sort()
|
||||||
|
w.messages = [msg for index, msg in sorting]
|
||||||
valid_warnings = 0
|
valid_warnings = 0
|
||||||
|
|
||||||
for warning in w.messages:
|
for warning in w.messages:
|
||||||
if skip_warning(warning):
|
if skip_warning(warning):
|
||||||
continue
|
continue
|
||||||
print warning
|
print(warning)
|
||||||
valid_warnings += 1
|
valid_warnings += 1
|
||||||
|
|
||||||
return valid_warnings
|
return valid_warnings
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ def main():
|
||||||
|
|
||||||
def _get_files(repo, **kwargs):
|
def _get_files(repo, **kwargs):
|
||||||
seen = set()
|
seen = set()
|
||||||
for rev in xrange(repo[kwargs['node']], len(repo)):
|
for rev in range(repo[kwargs['node']], len(repo)):
|
||||||
for file_ in repo[rev].files():
|
for file_ in repo[rev].files():
|
||||||
file_ = os.path.join(repo.root, file_)
|
file_ = os.path.join(repo.root, file_)
|
||||||
if file_ in seen or not os.path.exists(file_):
|
if file_ in seen or not os.path.exists(file_):
|
||||||
|
|
@ -118,6 +118,8 @@ def git_hook(complexity=-1, strict=False):
|
||||||
ext = os.path.splitext(filename)[-1]
|
ext = os.path.splitext(filename)[-1]
|
||||||
if ext != '.py':
|
if ext != '.py':
|
||||||
continue
|
continue
|
||||||
|
if not os.path.exists(filename):
|
||||||
|
continue
|
||||||
warnings += check_file(filename, complexity)
|
warnings += check_file(filename, complexity)
|
||||||
|
|
||||||
if strict:
|
if strict:
|
||||||
|
|
|
||||||
17
flake8/tests/test_flakes.py
Normal file
17
flake8/tests/test_flakes.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
from unittest import TestCase
|
||||||
|
from flake8.pyflakes import check
|
||||||
|
|
||||||
|
|
||||||
|
code = """
|
||||||
|
try:
|
||||||
|
pass
|
||||||
|
except ValueError as err:
|
||||||
|
print(err)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TestFlake(TestCase):
|
||||||
|
|
||||||
|
def test_exception(self):
|
||||||
|
warnings = check(code)
|
||||||
|
self.assertEqual(warnings, 0)
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import unittest
|
import unittest
|
||||||
import sys
|
import sys
|
||||||
from StringIO import StringIO
|
try:
|
||||||
|
from StringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
from flake8.mccabe import get_code_complexity
|
from flake8.mccabe import get_code_complexity
|
||||||
|
|
||||||
|
|
@ -33,6 +36,6 @@ class McCabeTest(unittest.TestCase):
|
||||||
self.assertEqual(get_code_complexity(_GLOBAL, 1), 2)
|
self.assertEqual(get_code_complexity(_GLOBAL, 1), 2)
|
||||||
self.out.seek(0)
|
self.out.seek(0)
|
||||||
res = self.out.read().strip().split('\n')
|
res = self.out.read().strip().split('\n')
|
||||||
wanted = ["stdin:5:1: 'a' is too complex (3)",
|
wanted = ["stdin:5:1: 'a' is too complex (4)",
|
||||||
'stdin:Loop 2 is too complex (1)']
|
'stdin:Loop 2 is too complex (2)']
|
||||||
self.assertEqual(res, wanted)
|
self.assertEqual(res, wanted)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import re
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
def skip_warning(warning):
|
def skip_warning(warning):
|
||||||
# XXX quick dirty hack, just need to keep the line in the warning
|
# XXX quick dirty hack, just need to keep the line in the warning
|
||||||
|
if not os.path.isfile(warning.filename):
|
||||||
|
return False
|
||||||
line = open(warning.filename).readlines()[warning.lineno - 1]
|
line = open(warning.filename).readlines()[warning.lineno - 1]
|
||||||
return skip_line(line)
|
return skip_line(line)
|
||||||
|
|
||||||
|
|
|
||||||
15
setup.py
15
setup.py
|
|
@ -1,7 +1,14 @@
|
||||||
try:
|
import sys
|
||||||
from setuptools import setup
|
|
||||||
except ImportError:
|
ispy3 = sys.version_info[0] == 3
|
||||||
from distutils.core import setup # NOQA
|
|
||||||
|
if ispy3:
|
||||||
|
from distutils.core import setup
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
from setuptools import setup # NOQA
|
||||||
|
except ImportError:
|
||||||
|
from distutils.core import setup # NOQA
|
||||||
|
|
||||||
from flake8 import __version__
|
from flake8 import __version__
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue