From 0b32d3373a3e77af35339fe1f6af1ec09c965207 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 13 Feb 2013 10:08:19 -0500 Subject: [PATCH 01/41] Add a possible file for collecting the ext work --- flake8/extensions.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 flake8/extensions.py diff --git a/flake8/extensions.py b/flake8/extensions.py new file mode 100644 index 0000000..e69de29 From 8a50be88b3846b4ef927206e83cc528b05d7b494 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 16:48:33 +0100 Subject: [PATCH 02/41] Prepare moving Pyflakes as an extension: setuptools becomes mandatory --- setup.py | 42 +++++++++++++----------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/setup.py b/setup.py index 29c3e1a..ad954df 100644 --- a/setup.py +++ b/setup.py @@ -1,33 +1,8 @@ -import sys -import os - -ispy3 = sys.version_info[0] == 3 -iswin = os.name == 'nt' - -kwargs = {} -scripts = ["flake8/flake8"] -if ispy3: - from distutils.core import setup # NOQA - if iswin: - scripts.append("scripts/flake8.cmd") -else: - try: - from setuptools import setup # NOQA - kwargs = { - 'entry_points': { - 'distutils.commands': ['flake8 = flake8.main:Flake8Command'], - 'console_scripts': ['flake8 = flake8.main:main'] - }, - 'tests_require': ['nose'], - 'test_suite': 'nose.collector', - } - except ImportError: - from distutils.core import setup # NOQA - if iswin: - scripts.append("scripts/flake8.cmd") +from setuptools import setup from flake8 import __version__ +scripts = ["flake8/flake8"] README = open('README.rst').read() setup( @@ -42,7 +17,11 @@ setup( url="http://bitbucket.org/tarek/flake8", packages=["flake8", "flake8.tests"], scripts=scripts, - install_requires=["pyflakes==0.6.1", "pep8==1.4.2"], + install_requires=[ + "setuptools", + "pyflakes==0.6.1", + "pep8==1.4.2", + ], long_description=README, classifiers=[ "Environment :: Console", @@ -52,5 +31,10 @@ setup( "Topic :: Software Development", "Topic :: Utilities", ], - **kwargs + entry_points={ + 'distutils.commands': ['flake8 = flake8.main:Flake8Command'], + 'console_scripts': ['flake8 = flake8.main:main'] + }, + tests_require=['nose'], + test_suite='nose.collector', ) From 844c95115450d63413aa14a728c94124f778f4c1 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 17:16:28 +0100 Subject: [PATCH 03/41] Make it clear that Flake8Command is currently broken --- flake8/main.py | 5 +++-- flake8/util.py | 16 ---------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/flake8/main.py b/flake8/main.py index 66ee41c..38e712d 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -5,7 +5,7 @@ import pyflakes.api import pyflakes.checker import select from flake8 import mccabe -from flake8.util import _initpep8, skip_file, get_parser, Flake8Reporter +from flake8.util import skip_file, get_parser, Flake8Reporter pep8style = None @@ -123,7 +123,8 @@ else: yield "%s.py" % filename def run(self): - _initpep8() + # XXX the setuptools command is currently broken + # _initpep8() # _get_python_files can produce the same file several # times, if one of its paths is a parent of another. Keep diff --git a/flake8/util.py b/flake8/util.py index 2dd6a80..23ee811 100644 --- a/flake8/util.py +++ b/flake8/util.py @@ -91,22 +91,6 @@ def skip_file(path, source=None): return _NOQA.search(content) is not None -def _initpep8(config_file=True): - # default pep8 setup - global pep8style - import pep8 - if pep8style is None: - pep8style = pep8.StyleGuide(config_file=config_file) - pep8style.options.physical_checks = pep8.find_checks('physical_line') - pep8style.options.logical_checks = pep8.find_checks('logical_line') - pep8style.options.counters = dict.fromkeys(pep8.BENCHMARK_KEYS, 0) - pep8style.options.messages = {} - if not pep8style.options.max_line_length: - pep8style.options.max_line_length = 79 - pep8style.args = [] - return pep8style - - error_mapping = { 'W402': (messages.UnusedImport,), 'W403': (messages.ImportShadowedByLoopVar,), From 20b8c5962ece6143c85fe9ba959fb6cd6ae843a5 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:18:49 +0100 Subject: [PATCH 04/41] Refactor to use extensions --- flake8/engine.py | 56 +++++++++++++++++ flake8/main.py | 131 ++++++++++---------------------------- flake8/util.py | 160 ++++++++++++----------------------------------- 3 files changed, 130 insertions(+), 217 deletions(-) create mode 100644 flake8/engine.py diff --git a/flake8/engine.py b/flake8/engine.py new file mode 100644 index 0000000..332c8c8 --- /dev/null +++ b/flake8/engine.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +import pep8 + +from flake8 import __version__ +from flake8.util import OrderedSet + + +def _register_extensions(): + """Register all the extensions.""" + extensions = OrderedSet() + extensions.add(('pep8', pep8.__version__)) + parser_hooks = [] + options_hooks = [] + try: + from pkg_resources import iter_entry_points + except ImportError: + pass + else: + for entry in iter_entry_points('flake8.extension'): + checker = entry.load() + pep8.register_check(checker, codes=[entry.name]) + extensions.add((checker.name, checker.version)) + if hasattr(checker, 'add_options'): + parser_hooks.append(checker.add_options) + if hasattr(checker, 'parse_options'): + options_hooks.append(checker.parse_options) + return extensions, parser_hooks, options_hooks + + +def get_parser(): + (extensions, parser_hooks, options_hooks) = _register_extensions() + details = ', '.join(['%s: %s' % ext for ext in extensions]) + parser = pep8.get_parser('flake8', '%s (%s)' % (__version__, details)) + for opt in ('--repeat', '--testsuite', '--doctest'): + try: + parser.remove_option(opt) + except ValueError: + pass + parser.add_option('--exit-zero', action='store_true', + help="exit with code 0 even if there are errors") + for parser_hook in parser_hooks: + parser_hook(parser) + parser.add_option('--install-hook', default=False, action='store_true', + help='Install the appropriate hook for this ' + 'repository.', dest='install_hook') + return parser, options_hooks + + +def get_style_guide(**kwargs): + """Parse the options and configure the checker.""" + kwargs['parser'], options_hooks = get_parser() + styleguide = pep8.StyleGuide(**kwargs) + options = styleguide.options + for options_hook in options_hooks: + options_hook(options) + return styleguide diff --git a/flake8/main.py b/flake8/main.py index 38e712d..4f8e120 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -1,91 +1,58 @@ import os import sys -import pep8 -import pyflakes.api -import pyflakes.checker import select -from flake8 import mccabe -from flake8.util import skip_file, get_parser, Flake8Reporter + +from flake8.engine import get_style_guide pep8style = None if sys.platform.startswith('win'): - pep8.DEFAULT_CONFIG = os.path.expanduser(r'~\.flake8') + DEFAULT_CONFIG = os.path.expanduser(r'~\.flake8') else: - pep8.DEFAULT_CONFIG = os.path.join( + DEFAULT_CONFIG = os.path.join( os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), 'flake8' ) -PROJECT_CONFIG = ['.flake8'] -PROJECT_CONFIG.extend(pep8.PROJECT_CONFIG) -pep8.PROJECT_CONFIG = tuple(PROJECT_CONFIG) - def main(): - global pep8style - # parse out our flags so pep8 doesn't get confused - parser = get_parser() + """Parse options and run checks on Python source.""" + # Prepare + flake8_style = get_style_guide(parse_argv=True, config_file=DEFAULT_CONFIG) + options = flake8_style.options - # We then have to re-parse argv to make sure pep8 is properly initialized - pep8style = pep8.StyleGuide(parse_argv=True, config_file=True, - parser=parser) - opts = pep8style.options - - if opts.install_hook: + if options.install_hook: from flake8.hooks import install_hook install_hook() - warnings = 0 - stdin = None + # Run the checkers + report = flake8_style.check_files() - complexity = opts.max_complexity - builtins = set(opts.builtins.split(',')) - if builtins: - orig_builtins = set(pyflakes.checker._MAGIC_GLOBALS) - pyflakes.checker._MAGIC_GLOBALS = orig_builtins | builtins - - # This is needed so we can ignore some items - pyflakes_reporter = Flake8Reporter(opts.ignore) - - if pep8style.paths and opts.filename is not None: - for path in _get_python_files(pep8style.paths): - if path == '-': - if stdin is None: - stdin = read_stdin() - warnings += check_code(stdin, opts.ignore, complexity) - else: - warnings += check_file(path, opts.ignore, complexity, - pyflakes_reporter) - else: - stdin = read_stdin() - warnings += check_code(stdin, opts.ignore, complexity, - pyflakes_reporter) - - if opts.exit_zero: - raise SystemExit(0) - - raise SystemExit(warnings > 0) + # Print the final report + options = flake8_style.options + if options.statistics: + report.print_statistics() + if options.benchmark: + report.print_benchmark() + if report.total_errors: + if options.count: + sys.stderr.write(str(report.total_errors) + '\n') + if not options.exit_zero: + raise SystemExit(1) def check_file(path, ignore=(), complexity=-1, reporter=None): - if pep8style.excluded(path): - return 0 - warnings = pyflakes.api.checkPath(path, reporter) - warnings -= reporter.ignored_warnings - warnings += pep8style.input_file(path) - if complexity > -1: - warnings += mccabe.get_module_complexity(path, complexity) - return warnings + flake8_style = get_style_guide( + config_file=DEFAULT_CONFIG, + ignore=ignore, max_complexity=complexity, reporter=reporter) + return flake8_style.input_file(path) def check_code(code, ignore=(), complexity=-1, reporter=None): - warnings = pyflakes.api.check(code, '', reporter) - warnings -= reporter.ignored_warnings - warnings += pep8style.input_file('-', lines=code.split('\n')) - if complexity > -1: - warnings += mccabe.get_code_complexity(code, complexity) - return warnings + flake8_style = get_style_guide( + config_file=DEFAULT_CONFIG, + ignore=ignore, max_complexity=complexity, reporter=reporter) + return flake8_style.input_file('-', lines=code.split('\n')) def read_stdin(): @@ -123,37 +90,7 @@ else: yield "%s.py" % filename def run(self): - # XXX the setuptools command is currently broken - # _initpep8() - - # _get_python_files can produce the same file several - # times, if one of its paths is a parent of another. Keep - # a set of checked files to de-duplicate. - checked = set() - - warnings = 0 - for path in _get_python_files(self.distribution_files()): - if path not in checked: - warnings += check_file(path) - checked.add(path) - - raise SystemExit(warnings > 0) - - -def _get_python_files(paths): - for path in paths: - if os.path.isdir(path): - for dirpath, dirnames, filenames in os.walk(path): - if pep8style.excluded(dirpath): - dirnames[:] = [] - continue - for filename in filenames: - if not filename.endswith('.py'): - continue - fullpath = os.path.join(dirpath, filename) - if not skip_file(fullpath) or pep8style.excluded(fullpath): - yield fullpath - - else: - if not skip_file(path) or pep8style.excluded(path): - yield path + flake8_style = get_style_guide(config_file=DEFAULT_CONFIG) + paths = self.distribution_files() + report = flake8_style.check_files(paths) + raise SystemExit(report.get_file_results() > 0) diff --git a/flake8/util.py b/flake8/util.py index 23ee811..3354274 100644 --- a/flake8/util.py +++ b/flake8/util.py @@ -1,73 +1,45 @@ +# -*- coding: utf-8 -*- from __future__ import with_statement +import os.path import re -import os -import sys -from io import StringIO -import pep8 -import pyflakes -from pyflakes import reporter, messages -pep8style = None +__all__ = ['ast', 'iter_child_nodes', 'OrderedSet', 'skip_file'] + +try: + import ast + iter_child_nodes = ast.iter_child_nodes +except ImportError: # Python 2.5 + import _ast as ast + + if 'decorator_list' not in ast.ClassDef._fields: + # Patch the missing attribute 'decorator_list' + ast.ClassDef.decorator_list = () + ast.FunctionDef.decorator_list = property(lambda s: s.decorators) + + def iter_child_nodes(node): + """ + Yield all direct child nodes of *node*, that is, all fields that + are nodes and all items of fields that are lists of nodes. + """ + if not node._fields: + return + for name in node._fields: + field = getattr(node, name, None) + if isinstance(field, ast.AST): + yield field + elif isinstance(field, list): + for item in field: + if isinstance(item, ast.AST): + yield item -def get_parser(): - """Create a custom OptionParser""" - from flake8 import __version__ - parser = pep8.get_parser() - - def version(option, opt, value, parser): - parser.print_usage() - parser.print_version() - sys.exit(0) - - parser.version = '{0} (pep8: {1}, pyflakes: {2})'.format( - __version__, pep8.__version__, pyflakes.__version__) - parser.remove_option('--version') - parser.add_option('--builtins', default='', dest='builtins', - help="append builtin functions to pyflakes' " - "_MAGIC_BUILTINS") - parser.add_option('--exit-zero', action='store_true', default=False, - help='Exit with status 0 even if there are errors') - parser.add_option('--max-complexity', default=-1, action='store', - type='int', help='McCabe complexity threshold') - parser.add_option('--install-hook', default=False, action='store_true', - help='Install the appropriate hook for this ' - 'repository.', dest='install_hook') - # don't overlap with pep8's verbose option - parser.add_option('-V', '--version', action='callback', - callback=version, - help='Print the version info for flake8') - parser.prog = os.path.basename(sys.argv[0]) - return parser - - -def skip_warning(warning, ignore=[]): - # XXX quick dirty hack, just need to keep the line in the warning - if not hasattr(warning, 'message') or ignore is None: - # McCabe's warnings cannot be skipped afaik, and they're all strings. - # And we'll get a TypeError otherwise - return False - if warning.message.split()[0] in ignore: - return True - if not os.path.isfile(warning.filename): - return False - - # XXX should cache the file in memory - with open(warning.filename) as f: - line = f.readlines()[warning.lineno - 1] - - return skip_line(line) - - -def skip_line(line): - def _noqa(line): - return line.strip().lower().endswith('# noqa') - skip = _noqa(line) - if not skip: - i = line.rfind(' #') - skip = _noqa(line[:i]) if i > 0 else False - return skip +class OrderedSet(list): + """List without duplicates.""" + __slots__ = () + def add(self, value): + if value not in self: + self.append(value) _NOQA = re.compile(r'flake8[:=]\s*noqa', re.I | re.M) @@ -78,61 +50,9 @@ def skip_file(path, source=None): # flake8: noqa """ if os.path.isfile(path): - f = open(path) - elif source: - f = StringIO(source) - else: + with open(path) as f: + source = f.read() + elif not source: return False - try: - content = f.read() - finally: - f.close() - return _NOQA.search(content) is not None - - -error_mapping = { - 'W402': (messages.UnusedImport,), - 'W403': (messages.ImportShadowedByLoopVar,), - 'W404': (messages.ImportStarUsed,), - 'W405': (messages.LateFutureImport,), - 'W801': (messages.RedefinedWhileUnused, - messages.RedefinedInListComp,), - 'W802': (messages.UndefinedName,), - 'W803': (messages.UndefinedExport,), - 'W804': (messages.UndefinedLocal, - messages.UnusedVariable,), - 'W805': (messages.DuplicateArgument,), - 'W806': (messages.Redefined,), -} - - -class Flake8Reporter(reporter.Reporter): - """Our own instance of a Reporter so that we can silence some messages.""" - class_mapping = dict((k, c) for (c, v) in error_mapping.items() for k in v) - - def __init__(self, ignore=None): - super(Flake8Reporter, self).__init__(sys.stdout, sys.stderr) - self.ignore = ignore or [] - self.ignored_warnings = 0 - - def flake(self, message): - classes = [error_mapping[i] for i in self.ignore if i in error_mapping] - - if (any(isinstance(message, c) for c in classes) or - skip_warning(message)): - self.ignored_warnings += 1 - return - m = self.to_str(message) - i = m.rfind(':') + 1 - message = '{0} {1}{2}'.format( - m[:i], self.class_mapping[message.__class__], m[i:] - ) - - super(Flake8Reporter, self).flake(message) - - def to_str(self, message): - try: - return unicode(message) - except NameError: - return str(message) + return _NOQA.search(source) is not None From 3ee195a8baf68f716df73ae6523bb490256101ae Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:19:30 +0100 Subject: [PATCH 05/41] Fix imports in flake8.run --- flake8/run.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/flake8/run.py b/flake8/run.py index e574c39..aca929e 100644 --- a/flake8/run.py +++ b/flake8/run.py @@ -2,10 +2,9 @@ """ Implementation of the command-line I{flake8} tool. """ -from flake8.hooks import git_hook, hg_hook # noqa +from flake8.hooks import git_hook, hg_hook # noqa +from flake8.main import check_code, check_file, Flake8Command # noqa from flake8.main import main -from flake8.main import (check_code, check_file, get_parser, # noqa - Flake8Command) # noqa if __name__ == '__main__': From 701ec32e3fcaf2766e44a2eae7c193a7300fd01e Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:21:37 +0100 Subject: [PATCH 06/41] Fix the commit hooks, hopefully --- flake8/hooks.py | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/flake8/hooks.py b/flake8/hooks.py index a99bae3..cd3e126 100644 --- a/flake8/hooks.py +++ b/flake8/hooks.py @@ -1,52 +1,52 @@ +from __future__ import with_statement import os import sys -from flake8.util import (_initpep8, pep8style, skip_file, get_parser, - ConfigParser) from subprocess import Popen, PIPE +try: + from configparser import ConfigParser +except ImportError: # Python 2 + from ConfigParser import ConfigParser + +from flake8.engine import get_parser, get_style_guide +from flake8.main import DEFAULT_CONFIG +from flake8.util import skip_file def git_hook(complexity=-1, strict=False, ignore=None, lazy=False): - from flake8.main import check_file - _initpep8() - if ignore: - pep8style.options.ignore = ignore - - warnings = 0 - gitcmd = "git diff-index --cached --name-only HEAD" if lazy: gitcmd = gitcmd.replace('--cached ', '') _, files_modified, _ = run(gitcmd) - for filename in files_modified: - ext = os.path.splitext(filename)[-1] - if ext != '.py': - continue - if not os.path.exists(filename): - continue - warnings += check_file(path=filename, ignore=ignore, - complexity=complexity) + + flake8_style = get_style_guide( + config_file=DEFAULT_CONFIG, + ignore=ignore, max_complexity=complexity) + report = flake8_style.check_files(files_modified) if strict: - return warnings + return report.get_file_results() return 0 def hg_hook(ui, repo, **kwargs): - from flake8.main import check_file complexity = ui.config('flake8', 'complexity', default=-1) config = ui.config('flake8', 'config', default=True) - _initpep8(config_file=config) - warnings = 0 + if config is True: + config = DEFAULT_CONFIG - for file_ in _get_files(repo, **kwargs): - warnings += check_file(file_, complexity) + flake8_style = get_style_guide( + config_file=config, + max_complexity=complexity) + + paths = _get_files(repo, **kwargs) + report = flake8_style.check_files(paths) strict = ui.configbool('flake8', 'strict', default=True) if strict: - return warnings + return report.get_file_results() return 0 From 212364d89c1c936c41a596fd1d571c70d20839fe Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:22:34 +0100 Subject: [PATCH 07/41] Create and enable the Pyflakes extension --- flake8/_pyflakes.py | 47 +++++++++++++++++++++++++++++++++++++++++++++ setup.py | 5 ++++- 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 flake8/_pyflakes.py diff --git a/flake8/_pyflakes.py b/flake8/_pyflakes.py new file mode 100644 index 0000000..41189e0 --- /dev/null +++ b/flake8/_pyflakes.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +import pyflakes +import pyflakes.checker + + +def patch_pyflakes(): + """Add error codes to Pyflakes messages.""" + codes = dict([line.split()[::-1] for line in ( + 'F401 UnusedImport', + 'F402 ImportShadowedByLoopVar', + 'F403 ImportStarUsed', + 'F404 LateFutureImport', + 'F810 Redefined', # XXX Obsolete? + 'F811 RedefinedWhileUnused', + 'F812 RedefinedInListComp', + 'F821 UndefinedName', + 'F822 UndefinedExport', + 'F823 UndefinedLocal', + 'F831 DuplicateArgument', + 'F841 UnusedVariable', + )]) + + for name, obj in vars(pyflakes.messages).items(): + if name[0].isupper() and obj.message: + obj.flint_msg = '%s %s' % (codes.get(name, 'F999'), obj.message) +patch_pyflakes() + + +class FlakesChecker(pyflakes.checker.Checker): + """Subclass the Pyflakes checker to conform with the flint API.""" + name = 'pyflakes' + version = pyflakes.__version__ + + @classmethod + def add_options(cls, parser): + parser.add_option('--builtins', + help="define more built-ins, comma separated") + parser.config_options.append('builtins') + + @classmethod + def parse_options(cls, options): + if options.builtins: + cls.builtIns = cls.builtIns.union(options.builtins.split(',')) + + def run(self): + for m in self.messages: + yield m.lineno, 0, (m.flint_msg % m.message_args), m.__class__ diff --git a/setup.py b/setup.py index ad954df..8db2373 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,10 @@ setup( ], entry_points={ 'distutils.commands': ['flake8 = flake8.main:Flake8Command'], - 'console_scripts': ['flake8 = flake8.main:main'] + 'console_scripts': ['flake8 = flake8.main:main'], + 'flake8.extension': [ + 'F = flake8._pyflakes:FlakesChecker', + ], }, tests_require=['nose'], test_suite='nose.collector', From 740e8b9ad6fb9740750026d8e13bcd48bf803b22 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:30:04 +0100 Subject: [PATCH 08/41] Remove empty module --- flake8/extensions.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 flake8/extensions.py diff --git a/flake8/extensions.py b/flake8/extensions.py deleted file mode 100644 index e69de29..0000000 From 1c6ed0526ab304aa72de0c2072f13fbe32f9770e Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:41:23 +0100 Subject: [PATCH 09/41] Upgrade McCabe checker as an extension; now it uses the AST --- flake8/mccabe.py | 197 +++++++++++++++++++++++++---------------------- setup.py | 1 + 2 files changed, 105 insertions(+), 93 deletions(-) diff --git a/flake8/mccabe.py b/flake8/mccabe.py index e93c87f..b6a5857 100644 --- a/flake8/mccabe.py +++ b/flake8/mccabe.py @@ -3,35 +3,26 @@ http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html MIT License. """ -try: - from compiler import parse # NOQA - iter_child_nodes = None # NOQA -except ImportError: - from ast import parse, iter_child_nodes # NOQA +from __future__ import with_statement import optparse import sys -from flake8.util import skip_warning from collections import defaultdict -WARNING_CODE = "W901" +from flake8.util import ast, iter_child_nodes + +version = 0.1 -class ASTVisitor: - - VERBOSE = 0 +class ASTVisitor(object): + """Performs a depth-first walk of the AST.""" 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: + for child in iter_child_nodes(node): self.dispatch(child, *args) def dispatch(self, node, *args): @@ -42,7 +33,6 @@ class ASTVisitor: 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): @@ -52,7 +42,7 @@ class ASTVisitor: self.dispatch(tree, *args) # XXX *args make sense? -class PathNode: +class PathNode(object): def __init__(self, name, look="circle"): self.name = name self.look = look @@ -65,7 +55,7 @@ class PathNode: return id(self) -class PathGraph: +class PathGraph(object): def __init__(self, name, entity, lineno): self.name = name self.entity = entity @@ -99,7 +89,7 @@ class PathGraphingAstVisitor(ASTVisitor): """ def __init__(self): - ASTVisitor.__init__(self) + super(PathGraphingAstVisitor, self).__init__() self.classname = "" self.graphs = {} self.reset() @@ -108,7 +98,11 @@ class PathGraphingAstVisitor(ASTVisitor): self.graph = None self.tail = None - def visitFunction(self, node): + 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) @@ -121,7 +115,7 @@ class PathGraphingAstVisitor(ASTVisitor): # closure pathnode = self.appendPathNode(name) self.tail = pathnode - self.default(node) + self.dispatch_list(node.body) bottom = PathNode("", look='point') self.graph.connect(self.tail, bottom) self.graph.connect(pathnode, bottom) @@ -130,16 +124,14 @@ class PathGraphingAstVisitor(ASTVisitor): self.graph = PathGraph(name, entity, node.lineno) pathnode = PathNode(name) self.tail = pathnode - self.default(node) + self.dispatch_list(node.body) self.graphs["%s%s" % (self.classname, node.name)] = self.graph self.reset() - visitFunctionDef = visitFunction - - def visitClass(self, node): + def visitClassDef(self, node): old_classname = self.classname self.classname += node.name + "." - self.default(node) + self.dispatch_list(node.body) self.classname = old_classname def appendPathNode(self, name): @@ -158,9 +150,9 @@ class PathGraphingAstVisitor(ASTVisitor): name = "Stmt %d" % lineno self.appendPathNode(name) - visitAssert = visitAssign = visitAssTuple = visitPrint = \ - visitPrintnl = visitRaise = visitSubscript = visitDecorators = \ - visitPass = visitDiscard = visitGlobal = visitReturn = \ + visitAssert = visitAssign = visitAugAssign = visitDelete = visitPrint = \ + visitRaise = visitYield = visitImport = visitCall = visitSubscript = \ + visitPass = visitContinue = visitBreak = visitGlobal = visitReturn = \ visitSimpleStatement def visitLoop(self, node): @@ -171,101 +163,119 @@ class PathGraphingAstVisitor(ASTVisitor): self.graph = PathGraph(name, name, node.lineno) pathnode = PathNode(name) self.tail = pathnode - self.default(node) + 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.default(node.body) + 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.else_ + # TODO: else clause in node.orelse visitFor = visitWhile = visitLoop def visitIf(self, node): name = "If %d" % node.lineno pathnode = self.appendPathNode(name) - if not pathnode: - return # TODO: figure out what to do with if's outside def's. loose_ends = [] - for t, n in node.tests: + self.dispatch_list(node.body) + loose_ends.append(self.tail) + if node.orelse: self.tail = pathnode - self.default(n) - loose_ends.append(self.tail) - if node.else_: - self.tail = pathnode - self.default(node.else_) + self.dispatch_list(node.orelse) loose_ends.append(self.tail) else: loose_ends.append(pathnode) - bottom = PathNode("", look='point') - for le in loose_ends: - self.graph.connect(le, bottom) - self.tail = bottom + if pathnode: + bottom = PathNode("", look='point') + for le in loose_ends: + self.graph.connect(le, bottom) + self.tail = bottom - # TODO: visitTryExcept - # TODO: visitTryFinally - # TODO: visitWith + 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 - # 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 visitWith(self, node): + name = "With %d" % node.lineno + self.appendPathNode(name) + self.dispatch_list(node.body) -def get_code_complexity(code, min=7, filename='stdin'): - complex = [] +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: - ast = parse(code) - except (AttributeError, SyntaxError): + 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 - visitor = PathGraphingAstVisitor() - visitor.preorder(ast, visitor) - for graph in visitor.graphs.values(): - if graph is None: - # ? - continue - if graph.complexity() >= min: - graph.filename = filename - if not skip_warning(graph): - msg = '%s:%d:1: %s %r is too complex (%d)' % ( - filename, - graph.lineno, - WARNING_CODE, - graph.entity, - graph.complexity(), - ) - complex.append(msg) + complx = [] + McCabeChecker.max_complexity = min_complexity + for lineno, offset, text, check in McCabeChecker(tree, filename): + complx.append('%s:%d:1: %s' % (filename, lineno, text)) - if len(complex) == 0: + if len(complx) == 0: return 0 - - print('\n'.join(complex)) - return len(complex) + print('\n'.join(complx)) + return len(complx) -def get_module_complexity(module_path, min=7): +def get_module_complexity(module_path, min_complexity=7): """Returns the complexity of a module""" - code = open(module_path, "rU").read() + '\n\n' - return get_code_complexity(code, min, filename=module_path) + with open(module_path, "rU") as mod: + code = mod.read() + return get_code_complexity(code, min_complexity, filename=module_path) def main(argv): @@ -278,10 +288,11 @@ def main(argv): options, args = opar.parse_args(argv) - text = open(args[0], "rU").read() + '\n\n' - ast = parse(text) + with open(args[0], "rU") as mod: + code = mod.read() + tree = compile(code, args[0], "exec", ast.PyCF_ONLY_AST) visitor = PathGraphingAstVisitor() - visitor.preorder(ast, visitor) + visitor.preorder(tree, visitor) if options.dot: print('graph {') diff --git a/setup.py b/setup.py index 8db2373..aab1ebf 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,7 @@ setup( 'console_scripts': ['flake8 = flake8.main:main'], 'flake8.extension': [ 'F = flake8._pyflakes:FlakesChecker', + 'C90 = flake8.mccabe:McCabeChecker', ], }, tests_require=['nose'], From 321bbce17fc56e5f3e5418a1670e8b515f5f91bb Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:53:01 +0100 Subject: [PATCH 10/41] Fix the get_*_complexity functions --- flake8/mccabe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake8/mccabe.py b/flake8/mccabe.py index b6a5857..8b7ac4b 100644 --- a/flake8/mccabe.py +++ b/flake8/mccabe.py @@ -262,7 +262,7 @@ def get_code_complexity(code, min_complexity=7, filename='stdin'): complx = [] McCabeChecker.max_complexity = min_complexity - for lineno, offset, text, check in McCabeChecker(tree, filename): + for lineno, offset, text, check in McCabeChecker(tree, filename).run(): complx.append('%s:%d:1: %s' % (filename, lineno, text)) if len(complx) == 0: From df6375c952f0681a3f66596ca77701db8a193b6b Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:53:56 +0100 Subject: [PATCH 11/41] Fix the codes in the tests --- flake8/tests/test_mccabe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flake8/tests/test_mccabe.py b/flake8/tests/test_mccabe.py index 887599f..1814699 100644 --- a/flake8/tests/test_mccabe.py +++ b/flake8/tests/test_mccabe.py @@ -36,6 +36,6 @@ class McCabeTest(unittest.TestCase): self.assertEqual(get_code_complexity(_GLOBAL, 1), 2) self.out.seek(0) res = self.out.read().strip().split('\n') - wanted = ["stdin:5:1: W901 'a' is too complex (4)", - "stdin:2:1: W901 'Loop 2' is too complex (2)"] + wanted = ["stdin:5:1: C901 'a' is too complex (4)", + "stdin:2:1: C901 'Loop 2' is too complex (2)"] self.assertEqual(res, wanted) From 015da9fabbfa1eb4cad723099ff61f734e3e0b8b Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 20:23:58 +0100 Subject: [PATCH 12/41] Remove unused global --- flake8/main.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/flake8/main.py b/flake8/main.py index 4f8e120..e5129f0 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -4,8 +4,6 @@ import select from flake8.engine import get_style_guide -pep8style = None - if sys.platform.startswith('win'): DEFAULT_CONFIG = os.path.expanduser(r'~\.flake8') else: From a13bdad3d8f01bf4057c896e5171f0cc8ea06b57 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 21:07:31 +0100 Subject: [PATCH 13/41] Wrong exit code returned by the setuptools Flake8Command --- flake8/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake8/main.py b/flake8/main.py index e5129f0..79c20e2 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -91,4 +91,4 @@ else: flake8_style = get_style_guide(config_file=DEFAULT_CONFIG) paths = self.distribution_files() report = flake8_style.check_files(paths) - raise SystemExit(report.get_file_results() > 0) + raise SystemExit(report.total_errors > 0) From 29b74f9b37a6efd61800487f2ef1c5432e0264a4 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 21:26:28 +0100 Subject: [PATCH 14/41] Wrong exit code for the VCS hooks --- flake8/hooks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flake8/hooks.py b/flake8/hooks.py index cd3e126..68df056 100644 --- a/flake8/hooks.py +++ b/flake8/hooks.py @@ -25,7 +25,7 @@ def git_hook(complexity=-1, strict=False, ignore=None, lazy=False): report = flake8_style.check_files(files_modified) if strict: - return report.get_file_results() + return report.total_errors return 0 @@ -46,7 +46,7 @@ def hg_hook(ui, repo, **kwargs): strict = ui.configbool('flake8', 'strict', default=True) if strict: - return report.get_file_results() + return report.total_errors return 0 From b721a38b3930a90cdb0b526dfc9b94c7b0f18cd9 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 22:03:56 +0100 Subject: [PATCH 15/41] Fix Pyflakes tests for Python 2.5, and add a reporter --- flake8/tests/test_flakes.py | 43 ++++++++++++++++++++++++++++++++----- flake8/tests/test_mccabe.py | 1 + 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/flake8/tests/test_flakes.py b/flake8/tests/test_flakes.py index b007955..79ac253 100644 --- a/flake8/tests/test_flakes.py +++ b/flake8/tests/test_flakes.py @@ -1,8 +1,30 @@ +# -*- coding: utf-8 -*- +import sys + from unittest import TestCase from pyflakes.api import check -code = """ +class FlakesTestReporter(object): + def __init__(self): + self.messages = [] + self.flakes = self.messages.append + + def unexpectedError(self, filename, msg): + self.flakes('[unexpectedError] %s: %s' % (filename, msg)) + + def syntaxError(self, filename, msg, lineno, offset, text): + self.flakes('[syntaxError] %s:%d: %s' % (filename, lineno, msg)) + + +code0 = """ +try: + pass +except ValueError, err: + print(err) +""" + +code1 = """ try: pass except ValueError as err: @@ -48,14 +70,25 @@ except foo.SomeException: class TestFlake(TestCase): def test_exception(self): - for c in (code, code2, code3): - warnings = check(code, '(stdin)') + codes = [code1, code2, code3] + if sys.version_info < (2, 6): + codes[0] = code0 + elif sys.version_info < (3,): + codes.insert(0, code0) + for code in codes: + reporter = FlakesTestReporter() + warnings = check(code, '(stdin)', reporter) + self.assertFalse(reporter.messages) self.assertEqual(warnings, 0) def test_from_import_exception_in_scope(self): - warnings = check(code_from_import_exception, '(stdin)') + reporter = FlakesTestReporter() + warnings = check(code_from_import_exception, '(stdin)', reporter) + self.assertFalse(reporter.messages) self.assertEqual(warnings, 0) def test_import_exception_in_scope(self): - warnings = check(code_import_exception, '(stdin)') + reporter = FlakesTestReporter() + warnings = check(code_import_exception, '(stdin)', reporter) + self.assertFalse(reporter.messages) self.assertEqual(warnings, 0) diff --git a/flake8/tests/test_mccabe.py b/flake8/tests/test_mccabe.py index 1814699..a220c83 100644 --- a/flake8/tests/test_mccabe.py +++ b/flake8/tests/test_mccabe.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import unittest import sys try: From 3a99cd18b6600ede5589e0e93a8600c2f6bf02de Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Thu, 14 Feb 2013 16:16:16 +0100 Subject: [PATCH 16/41] Turn it to an alpha version --- flake8/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake8/__init__.py b/flake8/__init__.py index a3332a5..1db7ef8 100644 --- a/flake8/__init__.py +++ b/flake8/__init__.py @@ -1,2 +1,2 @@ -__version__ = '2.0' +__version__ = '2.0a1' From 6055fcea2c7eb680f3f675a5db1576f5a04e5896 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Thu, 14 Feb 2013 17:21:34 +0100 Subject: [PATCH 17/41] Remove leftovers of the Flint merge --- flake8/_pyflakes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake8/_pyflakes.py b/flake8/_pyflakes.py index 41189e0..86d28ed 100644 --- a/flake8/_pyflakes.py +++ b/flake8/_pyflakes.py @@ -22,12 +22,12 @@ def patch_pyflakes(): for name, obj in vars(pyflakes.messages).items(): if name[0].isupper() and obj.message: - obj.flint_msg = '%s %s' % (codes.get(name, 'F999'), obj.message) + obj.flake8_msg = '%s %s' % (codes.get(name, 'F999'), obj.message) patch_pyflakes() class FlakesChecker(pyflakes.checker.Checker): - """Subclass the Pyflakes checker to conform with the flint API.""" + """Subclass the Pyflakes checker to conform with the flake8 API.""" name = 'pyflakes' version = pyflakes.__version__ @@ -44,4 +44,4 @@ class FlakesChecker(pyflakes.checker.Checker): def run(self): for m in self.messages: - yield m.lineno, 0, (m.flint_msg % m.message_args), m.__class__ + yield m.lineno, 0, (m.flake8_msg % m.message_args), m.__class__ From a6977c19161bbfcb2664cad7a2f35f5d3428fd93 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 18:39:54 +0100 Subject: [PATCH 18/41] Add Ian and me as contributors --- CONTRIBUTORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 3200c2f..6e7c642 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -14,6 +14,8 @@ Contributors (by order of appearance) : - Miki Tebeka - David Cramer - Peter Teichman +- Ian Cordasco - Oleg Broytman - Marc Labbé - Bruno Miguel Custódio +- Florent Xicluna From e4afd25312f2661b9b4cf4c8d75ee605f04b9701 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 18:41:29 +0100 Subject: [PATCH 19/41] Small refactoring in the hooks module --- flake8/hooks.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/flake8/hooks.py b/flake8/hooks.py index 68df056..efbc063 100644 --- a/flake8/hooks.py +++ b/flake8/hooks.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from __future__ import with_statement import os import sys @@ -20,8 +21,7 @@ def git_hook(complexity=-1, strict=False, ignore=None, lazy=False): _, files_modified, _ = run(gitcmd) flake8_style = get_style_guide( - config_file=DEFAULT_CONFIG, - ignore=ignore, max_complexity=complexity) + config_file=DEFAULT_CONFIG, ignore=ignore, max_complexity=complexity) report = flake8_style.check_files(files_modified) if strict: @@ -32,18 +32,16 @@ def git_hook(complexity=-1, strict=False, ignore=None, lazy=False): def hg_hook(ui, repo, **kwargs): complexity = ui.config('flake8', 'complexity', default=-1) + strict = ui.configbool('flake8', 'strict', default=True) config = ui.config('flake8', 'config', default=True) if config is True: config = DEFAULT_CONFIG - flake8_style = get_style_guide( - config_file=config, - max_complexity=complexity) - paths = _get_files(repo, **kwargs) - report = flake8_style.check_files(paths) - strict = ui.configbool('flake8', 'strict', default=True) + flake8_style = get_style_guide( + config_file=config, max_complexity=complexity) + report = flake8_style.check_files(paths) if strict: return report.total_errors @@ -53,9 +51,9 @@ def hg_hook(ui, repo, **kwargs): def run(command): p = Popen(command.split(), stdout=PIPE, stderr=PIPE) - p.wait() - return (p.returncode, [line.strip() for line in p.stdout.readlines()], - [line.strip() for line in p.stderr.readlines()]) + (stdout, stderr) = p.communicate() + return (p.returncode, [line.strip() for line in stdout.splitlines()], + [line.strip() for line in stderr.splitlines()]) def _get_files(repo, **kwargs): @@ -66,11 +64,8 @@ def _get_files(repo, **kwargs): if file_ in seen or not os.path.exists(file_): continue seen.add(file_) - if not file_.endswith('.py'): - continue - if skip_file(file_): - continue - yield file_ + if file_.endswith('.py') and not skip_file(file_): + yield file_ def find_vcs(): From be9f2a5ffbe134c3dd88dbe582e6aa5e27d4f913 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 18:42:31 +0100 Subject: [PATCH 20/41] Remove unused read_stdin, now ported to pep8 --- flake8/main.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/flake8/main.py b/flake8/main.py index 79c20e2..a52b536 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -1,6 +1,6 @@ +# -*- coding: utf-8 -*- import os import sys -import select from flake8.engine import get_style_guide @@ -53,16 +53,6 @@ def check_code(code, ignore=(), complexity=-1, reporter=None): return flake8_style.input_file('-', lines=code.split('\n')) -def read_stdin(): - # wait for 1 second on the stdin fd - reads, __, __ = select.select([sys.stdin], [], [], 1.) - if reads == []: - print('input not specified') - raise SystemExit(1) - - return sys.stdin.read() - - try: from setuptools import Command except ImportError: From 1496579fb2fd6db4833e2fc2c9995717d57706bf Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 18:44:17 +0100 Subject: [PATCH 21/41] Take into account that setuptools is no longer optional --- flake8/main.py | 45 +++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/flake8/main.py b/flake8/main.py index a52b536..72878c7 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -2,6 +2,8 @@ import os import sys +import setuptools + from flake8.engine import get_style_guide if sys.platform.startswith('win'): @@ -53,32 +55,27 @@ def check_code(code, ignore=(), complexity=-1, reporter=None): return flake8_style.input_file('-', lines=code.split('\n')) -try: - from setuptools import Command -except ImportError: - Flake8Command = None -else: - class Flake8Command(Command): - description = "Run flake8 on modules registered in setuptools" - user_options = [] +class Flake8Command(setuptools.Command): + description = "Run flake8 on modules registered in setuptools" + user_options = [] - def initialize_options(self): - pass + def initialize_options(self): + pass - def finalize_options(self): - pass + def finalize_options(self): + pass - def distribution_files(self): - if self.distribution.packages: - for package in self.distribution.packages: - yield package.replace(".", os.path.sep) + def distribution_files(self): + if self.distribution.packages: + for package in self.distribution.packages: + yield package.replace(".", os.path.sep) - if self.distribution.py_modules: - for filename in self.distribution.py_modules: - yield "%s.py" % filename + if self.distribution.py_modules: + for filename in self.distribution.py_modules: + yield "%s.py" % filename - def run(self): - flake8_style = get_style_guide(config_file=DEFAULT_CONFIG) - paths = self.distribution_files() - report = flake8_style.check_files(paths) - raise SystemExit(report.total_errors > 0) + def run(self): + flake8_style = get_style_guide(config_file=DEFAULT_CONFIG) + paths = self.distribution_files() + report = flake8_style.check_files(paths) + raise SystemExit(report.total_errors > 0) From fa092c0294bdd0a57f1031b479c32342272b8139 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 18:45:48 +0100 Subject: [PATCH 22/41] Remove the obsolete argument 'reporter' of check_code and check_file --- flake8/main.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/flake8/main.py b/flake8/main.py index 72878c7..cbcaeef 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -41,18 +41,16 @@ def main(): raise SystemExit(1) -def check_file(path, ignore=(), complexity=-1, reporter=None): +def check_file(path, ignore=(), complexity=-1): flake8_style = get_style_guide( - config_file=DEFAULT_CONFIG, - ignore=ignore, max_complexity=complexity, reporter=reporter) + config_file=DEFAULT_CONFIG, ignore=ignore, max_complexity=complexity) return flake8_style.input_file(path) -def check_code(code, ignore=(), complexity=-1, reporter=None): +def check_code(code, ignore=(), complexity=-1): flake8_style = get_style_guide( - config_file=DEFAULT_CONFIG, - ignore=ignore, max_complexity=complexity, reporter=reporter) - return flake8_style.input_file('-', lines=code.split('\n')) + config_file=DEFAULT_CONFIG, ignore=ignore, max_complexity=complexity) + return flake8_style.input_file('-', lines=code.splitlines()) class Flake8Command(setuptools.Command): From 7362d57c24a3e06bf89561ae4a2b54366d5b3a94 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 21:14:13 +0100 Subject: [PATCH 23/41] Restore the whole-file-skip feature --- flake8/engine.py | 22 +++++++++++++++++++++- flake8/hooks.py | 3 +-- flake8/util.py | 20 +------------------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/flake8/engine.py b/flake8/engine.py index 332c8c8..bcbad54 100644 --- a/flake8/engine.py +++ b/flake8/engine.py @@ -1,9 +1,13 @@ # -*- coding: utf-8 -*- +import re + import pep8 from flake8 import __version__ from flake8.util import OrderedSet +_flake8_noqa = re.compile(r'flake8[:=]\s*noqa', re.I).search + def _register_extensions(): """Register all the extensions.""" @@ -46,10 +50,26 @@ def get_parser(): return parser, options_hooks +class StyleGuide(pep8.StyleGuide): + # Backward compatibility pep8 <= 1.4.2 + checker_class = pep8.Checker + + def input_file(self, filename, lines=None, expected=None, line_offset=0): + """Run all checks on a Python source file.""" + if self.options.verbose: + print('checking %s' % filename) + fchecker = self.checker_class( + filename, lines=lines, options=self.options) + # Any "# flake8: noqa" line? + if any(_flake8_noqa(line) for line in fchecker.lines): + return 0 + return fchecker.check_all(expected=expected, line_offset=line_offset) + + def get_style_guide(**kwargs): """Parse the options and configure the checker.""" kwargs['parser'], options_hooks = get_parser() - styleguide = pep8.StyleGuide(**kwargs) + styleguide = StyleGuide(**kwargs) options = styleguide.options for options_hook in options_hooks: options_hook(options) diff --git a/flake8/hooks.py b/flake8/hooks.py index efbc063..37c1b60 100644 --- a/flake8/hooks.py +++ b/flake8/hooks.py @@ -10,7 +10,6 @@ except ImportError: # Python 2 from flake8.engine import get_parser, get_style_guide from flake8.main import DEFAULT_CONFIG -from flake8.util import skip_file def git_hook(complexity=-1, strict=False, ignore=None, lazy=False): @@ -64,7 +63,7 @@ def _get_files(repo, **kwargs): if file_ in seen or not os.path.exists(file_): continue seen.add(file_) - if file_.endswith('.py') and not skip_file(file_): + if file_.endswith('.py'): yield file_ diff --git a/flake8/util.py b/flake8/util.py index 3354274..771cca0 100644 --- a/flake8/util.py +++ b/flake8/util.py @@ -1,9 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import with_statement -import os.path -import re -__all__ = ['ast', 'iter_child_nodes', 'OrderedSet', 'skip_file'] +__all__ = ['ast', 'iter_child_nodes', 'OrderedSet'] try: import ast @@ -40,19 +38,3 @@ class OrderedSet(list): def add(self, value): if value not in self: self.append(value) - -_NOQA = re.compile(r'flake8[:=]\s*noqa', re.I | re.M) - - -def skip_file(path, source=None): - """Returns True if this header is found in path - - # flake8: noqa - """ - if os.path.isfile(path): - with open(path) as f: - source = f.read() - elif not source: - return False - - return _NOQA.search(source) is not None From 20b49ee2578f338e441e9d9227cb740ff6d4b8db Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:30:24 +0100 Subject: [PATCH 24/41] setup.py: reorder entries --- setup.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index aab1ebf..4e162f9 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ setup( license="MIT", version=__version__, description="code checking using pep8 and pyflakes", + long_description=README, author="Tarek Ziade", author_email="tarek@ziade.org", maintainer="Ian Cordasco", @@ -22,15 +23,6 @@ setup( "pyflakes==0.6.1", "pep8==1.4.2", ], - long_description=README, - classifiers=[ - "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python", - "Topic :: Software Development", - "Topic :: Utilities", - ], entry_points={ 'distutils.commands': ['flake8 = flake8.main:Flake8Command'], 'console_scripts': ['flake8 = flake8.main:main'], @@ -39,6 +31,14 @@ setup( 'C90 = flake8.mccabe:McCabeChecker', ], }, + classifiers=[ + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Topic :: Software Development", + "Topic :: Utilities", + ], tests_require=['nose'], test_suite='nose.collector', ) From 3e1452d10eb0c1f1c3c40bead1646085a1ad1588 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:32:04 +0100 Subject: [PATCH 25/41] setup.py: remove 'scripts' which is redundant with setuptools entry points; issue #70 --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 4e162f9..c7101a3 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,6 @@ from setuptools import setup from flake8 import __version__ -scripts = ["flake8/flake8"] README = open('README.rst').read() setup( @@ -17,7 +16,6 @@ setup( maintainer_email="graffatcolmingov@gmail.com", url="http://bitbucket.org/tarek/flake8", packages=["flake8", "flake8.tests"], - scripts=scripts, install_requires=[ "setuptools", "pyflakes==0.6.1", From bcbf1b8eae5ba8340401fe072c12fbc71609a970 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:47:54 +0100 Subject: [PATCH 26/41] setup.py: allow upgrade of dependencies --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index c7101a3..f3131cc 100644 --- a/setup.py +++ b/setup.py @@ -18,8 +18,8 @@ setup( packages=["flake8", "flake8.tests"], install_requires=[ "setuptools", - "pyflakes==0.6.1", - "pep8==1.4.2", + "pyflakes >= 0.6.1", + "pep8 >= 1.4.2", ], entry_points={ 'distutils.commands': ['flake8 = flake8.main:Flake8Command'], From b8aab10d5f7c6d07390f8e89e70e76135f40978d Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:50:12 +0100 Subject: [PATCH 27/41] setup.py: add classifiers for Python 2/3 compatibility --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index f3131cc..82877e1 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,8 @@ setup( "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", "Topic :: Software Development", "Topic :: Utilities", ], From 2e76b4191ab20e376c4fccf1bb6b02f5c19386e9 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:50:12 +0100 Subject: [PATCH 28/41] setup.py: new topic classifiers --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 82877e1..4e9cf4f 100644 --- a/setup.py +++ b/setup.py @@ -36,8 +36,8 @@ setup( "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", - "Topic :: Software Development", - "Topic :: Utilities", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Software Development :: Quality Assurance", ], tests_require=['nose'], test_suite='nose.collector', From 83571710cb23bc4b53e39bc8b02e5ee98587e9fe Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:50:12 +0100 Subject: [PATCH 29/41] setup.py: update description --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4e9cf4f..d19e3a2 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name="flake8", license="MIT", version=__version__, - description="code checking using pep8 and pyflakes", + description="the modular source code checker: pep8, pyflakes and co", long_description=README, author="Tarek Ziade", author_email="tarek@ziade.org", From 9a8d85d22f3a0eb49d75b259ca7fcf332fbef7ac Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:50:12 +0100 Subject: [PATCH 30/41] Move the changelog to CHANGES.rst --- CHANGES.rst | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++ README.rst | 135 ---------------------------------------------------- setup.py | 13 ++++- 3 files changed, 143 insertions(+), 137 deletions(-) create mode 100644 CHANGES.rst diff --git a/CHANGES.rst b/CHANGES.rst new file mode 100644 index 0000000..fcddf7b --- /dev/null +++ b/CHANGES.rst @@ -0,0 +1,132 @@ +CHANGES +======= + +2.0.0 - 2013-01-xx +------------------ + +- Fixes #13: pep8 and pyflakes are now external dependencies +- Split run.py into main.py and hooks.py for better logic +- Expose our parser for our users +- New feature: Install git and hg hooks automagically +- By relying on pyflakes (0.6.1), we also fixed #45 and #35 + +1.7.0 - 2012-12-21 +------------------ + +- Fixes part of #35: Exception for no WITHITEM being an attribute of Checker + for python 3.3 +- Support stdin +- Incorporate @phd's builtins pull request +- Fix the git hook +- Update pep8.py to the latest version + +1.6.2 - 2012-11-25 +------------------ + +- fixed the NameError: global name 'message' is not defined (#46) + + +1.6.1 - 2012-11-24 +------------------ + +- fixed the mercurial hook, a change from a previous patch was not properly + applied +- fixed an assumption about warnings/error messages that caused an exception + to be thrown when McCabe is used + +1.6 - 2012-11-16 +---------------- + +- changed the signatures of the ``check_file`` function in flake8/run.py, + ``skip_warning`` in flake8/util.py and the ``check``, ``checkPath`` + functions in flake8/pyflakes.py. +- fix ``--exclude`` and ``--ignore`` command flags (#14, #19) +- fix the git hook that wasn't catching files not already added to the index + (#29) +- pre-emptively includes the addition to pep8 to ignore certain lines. Add ``# + nopep8`` to the end of a line to ignore it. (#37) +- ``check_file`` can now be used without any special prior setup (#21) +- unpacking exceptions will no longer cause an exception (#20) +- fixed crash on non-existant file (#38) + + + +1.5 - 2012-10-13 +---------------- + +- fixed the stdin +- make sure mccabe catches the syntax errors as warnings +- pep8 upgrade +- added max_line_length default value +- added Flake8Command and entry points is setuptools is around +- using the setuptools console wrapper when available + + +1.4 - 2012-07-12 +---------------- + +- git_hook: Only check staged changes for compliance +- use pep8 1.2 + + +1.3.1 - 2012-05-19 +------------------ + +- fixed support for Python 2.5 + + +1.3 - 2012-03-12 +---------------- + +- fixed false W402 warning on exception blocks. + + +1.2 - 2012-02-12 +---------------- + +- added a git hook +- now python 3 compatible +- mccabe and pyflakes have warning codes like pep8 now + + +1.1 - 2012-02-14 +---------------- + +- fixed the value returned by --version +- allow the flake8: header to be more generic +- fixed the "hg hook raises 'physical lines'" bug +- allow three argument form of raise +- now uses setuptools if available, for 'develop' command + +1.0 - 2011-11-29 +---------------- + +- Deactivates by default the complexity checker +- Introduces the complexity option in the HG hook and the command line. + + +0.9 - 2011-11-09 +---------------- + +- update pep8 version to 0.6.1 +- mccabe check: gracefully handle compile failure + +0.8 - 2011-02-27 +---------------- + +- fixed hg hook +- discard unexisting files on hook check + + +0.7 - 2010-02-18 +---------------- + +- Fix pep8 intialization when run through Hg +- Make pep8 short options work when run throug the command line +- skip duplicates when controlling files via Hg + + +0.6 - 2010-02-15 +---------------- + +- Fix the McCabe metric on some loops diff --git a/README.rst b/README.rst index 7b415d2..f6b5cc4 100644 --- a/README.rst +++ b/README.rst @@ -225,138 +225,3 @@ flakey: McCabe: - W901: '' is too complex ('') - -CHANGES -======= - -2.0.0 - 2013-01-xx ------------------- - -- Fixes #13: pep8 and pyflakes are now external dependencies -- Split run.py into main.py and hooks.py for better logic -- Expose our parser for our users -- New feature: Install git and hg hooks automagically -- By relying on pyflakes (0.6.1), we also fixed #45 and #35 - -1.7.0 - 2012-12-21 ------------------- - -- Fixes part of #35: Exception for no WITHITEM being an attribute of Checker - for python 3.3 -- Support stdin -- Incorporate @phd's builtins pull request -- Fix the git hook -- Update pep8.py to the latest version - -1.6.2 - 2012-11-25 ------------------- - -- fixed the NameError: global name 'message' is not defined (#46) - - -1.6.1 - 2012-11-24 ------------------- - -- fixed the mercurial hook, a change from a previous patch was not properly - applied -- fixed an assumption about warnings/error messages that caused an exception - to be thrown when McCabe is used - -1.6 - 2012-11-16 ----------------- - -- changed the signatures of the ``check_file`` function in flake8/run.py, - ``skip_warning`` in flake8/util.py and the ``check``, ``checkPath`` - functions in flake8/pyflakes.py. -- fix ``--exclude`` and ``--ignore`` command flags (#14, #19) -- fix the git hook that wasn't catching files not already added to the index - (#29) -- pre-emptively includes the addition to pep8 to ignore certain lines. Add ``# - nopep8`` to the end of a line to ignore it. (#37) -- ``check_file`` can now be used without any special prior setup (#21) -- unpacking exceptions will no longer cause an exception (#20) -- fixed crash on non-existant file (#38) - - - -1.5 - 2012-10-13 ----------------- - -- fixed the stdin -- make sure mccabe catches the syntax errors as warnings -- pep8 upgrade -- added max_line_length default value -- added Flake8Command and entry points is setuptools is around -- using the setuptools console wrapper when available - - -1.4 - 2012-07-12 ----------------- - -- git_hook: Only check staged changes for compliance -- use pep8 1.2 - - -1.3.1 - 2012-05-19 ------------------- - -- fixed support for Python 2.5 - - -1.3 - 2012-03-12 ----------------- - -- fixed false W402 warning on exception blocks. - - -1.2 - 2012-02-12 ----------------- - -- added a git hook -- now python 3 compatible -- mccabe and pyflakes have warning codes like pep8 now - - -1.1 - 2012-02-14 ----------------- - -- fixed the value returned by --version -- allow the flake8: header to be more generic -- fixed the "hg hook raises 'physical lines'" bug -- allow three argument form of raise -- now uses setuptools if available, for 'develop' command - -1.0 - 2011-11-29 ----------------- - -- Deactivates by default the complexity checker -- Introduces the complexity option in the HG hook and the command line. - - -0.9 - 2011-11-09 ----------------- - -- update pep8 version to 0.6.1 -- mccabe check: gracefully handle compile failure - -0.8 - 2011-02-27 ----------------- - -- fixed hg hook -- discard unexisting files on hook check - - -0.7 - 2010-02-18 ----------------- - -- Fix pep8 intialization when run through Hg -- Make pep8 short options work when run throug the command line -- skip duplicates when controlling files via Hg - - -0.6 - 2010-02-15 ----------------- - -- Fix the McCabe metric on some loops - - diff --git a/setup.py b/setup.py index d19e3a2..054073b 100644 --- a/setup.py +++ b/setup.py @@ -1,15 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import with_statement from setuptools import setup from flake8 import __version__ -README = open('README.rst').read() + +def get_long_description(): + descr = [] + for fname in ('README.rst', 'CHANGES.rst'): + with open(fname) as f: + descr.append(f.read()) + return '\n\n'.join(descr) + setup( name="flake8", license="MIT", version=__version__, description="the modular source code checker: pep8, pyflakes and co", - long_description=README, + long_description=get_long_description(), author="Tarek Ziade", author_email="tarek@ziade.org", maintainer="Ian Cordasco", From 472b186eb3032137d117789a5f52ff1dba1cd04d Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:50:12 +0100 Subject: [PATCH 31/41] setup.py: do not import the module to read the __version__ --- setup.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 054073b..70693e8 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,12 @@ from __future__ import with_statement from setuptools import setup -from flake8 import __version__ + +def get_version(fname='flake8/__init__.py'): + with open(fname) as f: + for line in f: + if line.startswith('__version__'): + return eval(line.split('=')[-1]) def get_long_description(): @@ -16,7 +21,7 @@ def get_long_description(): setup( name="flake8", license="MIT", - version=__version__, + version=get_version(), description="the modular source code checker: pep8, pyflakes and co", long_description=get_long_description(), author="Tarek Ziade", From 8af0f1ed98f8ce8555538747d4fdb60c1d2e45c4 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:50:12 +0100 Subject: [PATCH 32/41] Add information to the README.rst --- README.rst | 183 +++++++++++++++++++++++++++++------------------------ 1 file changed, 101 insertions(+), 82 deletions(-) diff --git a/README.rst b/README.rst index f6b5cc4..912ba17 100644 --- a/README.rst +++ b/README.rst @@ -8,22 +8,20 @@ Flake8 is a wrapper around these tools: - pep8 - Ned's McCabe script -Flake8 runs all tools by launching the single 'flake8' script, but ignores pep8 -and PyFlakes extended options and just uses defaults. It displays the warnings -in a per-file, merged output. +Flake8 runs all the tools by launching the single 'flake8' script. +It displays the warnings in a per-file, merged output. It also adds a few features: -- files that contains with this header are skipped:: +- files that contain this line are skipped:: # flake8: noqa -- lines that contain a "# NOQA" comment at the end will not issue pyflakes - warnings. -- lines that contain a "# NOPEP8" comment at the end will not issue pep8 - warnings. -- a Mercurial hook. +- lines that contain a "# noqa" comment at the end will not issue warnings. +- a Git and a Mercurial hook. - a McCabe complexity checker. +- extendable through ``flake8.extension`` entry points + QuickStart ========== @@ -37,13 +35,14 @@ To run flake8 just invoke it against any directory or Python module:: coolproject/mod.py:1028: local variable 'errors' is assigned to but never used coolproject/mod.py:625:17: E225 missing whitespace around operato -The output of PyFlakes *and* pep8 is merged and returned. +The output of PyFlakes *and* pep8 (and the optional plugins) are merged +and returned. -flake8 offers an extra option: --max-complexity, which will emit a warning if the -McCabe complexityu of a function is higher that the value. By default it's +flake8 offers an extra option: --max-complexity, which will emit a warning if +the McCabe complexity of a function is higher than the value. By default it's deactivated:: - $ bin/flake8 --max-complexity 12 flake8 + $ flake8 --max-complexity 12 flake8 coolproject/mod.py:97: 'shutil' imported but unused coolproject/mod.py:729: redefinition of function 'readlines' from line 723 coolproject/mod.py:1028: local variable 'errors' is assigned to but never used @@ -52,16 +51,37 @@ deactivated:: coolproject/mod.py:939:1: 'Checker.check_all' is too complex (12) coolproject/mod.py:1204:1: 'selftest' is too complex (14) -This feature is quite useful to detect over-complex code. According to McCabe, anything -that goes beyond 10 is too complex. +This feature is quite useful to detect over-complex code. According to McCabe, +anything that goes beyond 10 is too complex. See https://en.wikipedia.org/wiki/Cyclomatic_complexity. +Configuration +------------- + +The behaviour may be configured at two levels. + +The user settings are read from the ``~/.config/flake8`` file. +Example:: + + [flake8] + ignore = E226,E302,E41 + max-line-length = 160 + +At the project level, a ``tox.ini`` file or a ``setup.cfg`` file is read +if present. Only the first file is considered. If this file does not +have a ``[flake8]`` section, no project specific configuration is loaded. + +If the ``ignore`` option is not in the configuration and not in the arguments, +only the error codes ``E226`` and ``E241/E242`` are ignored +(see codes in the table below). + + Mercurial hook ============== -To use the Mercurial hook on any *commit* or *qrefresh*, change your .hg/rc file -like this:: +To use the Mercurial hook on any *commit* or *qrefresh*, change your .hg/hgrc +file like this:: [hooks] commit = python:flake8.run.hg_hook @@ -73,11 +93,12 @@ like this:: If *strict* option is set to **1**, any warning will block the commit. When -*strict* is set to **0**, warnings are just displayed in the standard output. +*strict* is set to **0**, warnings are just printed to the standard output. *complexity* defines the maximum McCabe complexity allowed before a warning -is emited. If you don't specify it it's just ignored. If specified, must -be a positive value. 12 is usually a good value. +is emitted. If you don't specify it, it's just ignored. If specified, it must +be a positive value. 12 is usually a good value. + Git hook ======== @@ -97,18 +118,18 @@ To use the Git hook on any *commit*, add a **pre-commit** file in the If *strict* option is set to **True**, any warning will block the commit. When -*strict* is set to **False** or omited, warnings are just displayed in the +*strict* is set to **False** or omitted, warnings are just printed to the standard output. *complexity* defines the maximum McCabe complexity allowed before a warning -is emited. If you don't specify it or set it to **-1**, it's just ignored. -If specified, it must be a positive value. 12 is usually a good value. +is emitted. If you don't specify it or set it to **-1**, it's just ignored. +If specified, it must be a positive value. 12 is usually a good value. -*lazy* when set to ``True`` will also take into account files not added to the +*lazy* when set to ``True`` will also take into account files not added to the index. Also, make sure the file is executable and adapt the shebang line so it -point to your python interpreter. +points to your Python interpreter. Buildout integration @@ -134,7 +155,7 @@ setuptools integration ====================== If setuptools is available, Flake8 provides a command that checks the -Python files declared by your project. To use it, add flake8 to your +Python files declared by your project. To use it, add flake8 to your setup_requires:: setup( @@ -147,8 +168,8 @@ setup_requires:: ) Running ``python setup.py flake8`` on the command line will check the -files listed in your ``py_modules`` and ``packages``. If any warnings -are found, the command will exit with an error code:: +files listed in your ``py_modules`` and ``packages``. If any warning +is found, the command will exit with an error code:: $ python setup.py flake8 @@ -160,68 +181,66 @@ Original projects Flake8 is just a glue project, all the merits go to the creators of the original projects: -- pep8: https://github.com/jcrocholl/pep8/ -- PyFlakes: http://divmod.org/trac/wiki/DivmodPyflakes -- flakey: https://bitbucket.org/icordasc/flakey +- pep8: https://github.com/jcrocholl/pep8 +- PyFlakes: https://launchpad.net/pyflakes - McCabe: http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html + Warning / Error codes ===================== -Below are lists of all warning and error codes flake8 will generate, broken -out by component. +The convention of Flake8 is to assign a code to each error or warning, like +the ``pep8`` tool. These codes are used to configure the list of errors +which are selected or ignored. -pep8: +Each code consists of an upper case ASCII letter followed by three digits. +The recommendation is to use a different prefix for each plugin. -- E101: indentation contains mixed spaces and tabs -- E111: indentation is not a multiple of four -- E112: expected an indented block -- E113: unexpected indentation -- E201: whitespace after char -- E202: whitespace before char -- E203: whitespace before char -- E211: whitespace before text -- E223: tab / multiple spaces before operator -- E224: tab / multiple spaces after operator -- E225: missing whitespace around operator -- E225: missing whitespace around operator -- E231: missing whitespace after char -- E241: multiple spaces after separator -- E242: tab after separator -- E251: no spaces around keyword / parameter equals -- E262: inline comment should start with '# ' -- E301: expected 1 blank line, found 0 -- E302: expected 2 blank lines, found -- E303: too many blank lines () -- E304: blank lines found after function decorator -- E401: multiple imports on one line -- E501: line too long ( characters) -- E701: multiple statements on one line (colon) -- E702: multiple statements on one line (semicolon) -- W191: indentation contains tabs -- W291: trailing whitespace -- W292: no newline at end of file -- W293: blank line contains whitespace -- W391: blank line at end of file -- W601: .has_key() is deprecated, use 'in' -- W602: deprecated form of raising exception -- W603: '<>' is deprecated, use '!=' -- W604: backticks are deprecated, use 'repr()' +A list of the known prefixes is published below: -flakey: +- ``E***``/``W***``: `pep8 errors and warnings + `_ +- ``F***``: PyFlakes codes (see below) +- ``C9**``: McCabe complexity plugin `mccabe + `_ +- ``N8**``: Naming Conventions plugin `flint-naming + `_ -- W402: imported but unused -- W403: import from line shadowed by loop variable -- W404: 'from import ``*``' used; unable to detect undefined names -- W405: future import(s) after other statements -- W801: redefinition of unused from line -- W802: undefined name -- W803: undefined name in __all__ -- W804: local variable (defined in enclosing scope on line ) referenced before assignment -- W805: duplicate argument in function definition -- W806: redefinition of function from line -- W806: local variable is assigned to but never used -McCabe: +The original PyFlakes does not provide error codes. Flake8 patches the +PyFlakes messages to add the following codes: -- W901: '' is too complex ('') ++------+--------------------------------------------------------------------+ +| code | sample message | ++======+====================================================================+ +| F401 | ``module`` imported but unused | ++------+--------------------------------------------------------------------+ +| F402 | import ``module`` from line ``N`` shadowed by loop variable | ++------+--------------------------------------------------------------------+ +| F403 | 'from ``module`` import \*' used; unable to detect undefined names | ++------+--------------------------------------------------------------------+ +| F404 | future import(s) ``name`` after other statements | ++------+--------------------------------------------------------------------+ ++------+--------------------------------------------------------------------+ +| F811 | redefinition of unused ``name`` from line ``N`` | ++------+--------------------------------------------------------------------+ +| F812 | list comprehension redefines ``name`` from line ``N`` | ++------+--------------------------------------------------------------------+ +| F821 | undefined name ``name`` | ++------+--------------------------------------------------------------------+ +| F822 | undefined name ``name`` in __all__ | ++------+--------------------------------------------------------------------+ +| F823 | local variable ``name`` ... referenced before assignment | ++------+--------------------------------------------------------------------+ +| F831 | duplicate argument ``name`` in function definition | ++------+--------------------------------------------------------------------+ +| F841 | local variable ``name`` is assigned to but never used | ++------+--------------------------------------------------------------------+ + + +Links +----- + +* `pep8 documentation `_ + +* `flake8 documentation `_ From a593dd01a462eae39ec5786f13ff2c97622ac581 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Thu, 21 Feb 2013 00:00:54 +0100 Subject: [PATCH 33/41] 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=[ From 6bf49f848f351b0e18fae312551e282de24325b6 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Thu, 21 Feb 2013 23:56:35 +0100 Subject: [PATCH 34/41] Do not copy the licenses for the dependencies --- LICENSE | 72 --------------------------------------------------------- 1 file changed, 72 deletions(-) diff --git a/LICENSE b/LICENSE index 7d4564e..c8c1f45 100644 --- a/LICENSE +++ b/LICENSE @@ -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) 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. - From 11b52513630aea52e8229ea95e6271acd9ffd1eb Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Thu, 21 Feb 2013 23:59:41 +0100 Subject: [PATCH 35/41] Include the *.rst files in MANIFEST.in --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 68c63de..9b622ad 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ +include *.rst include CONTRIBUTORS.txt -include README include LICENSE recursive-include scripts flake8.* From 467b9f5c2923eeaae32358d423983bdf21d586b2 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Fri, 22 Feb 2013 08:30:37 +0100 Subject: [PATCH 36/41] Upgrade requirements to pep8 1.4.3 and mccabe 0.2 --- CHANGES.rst | 8 ++++++-- setup.py | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index fcddf7b..473c9a7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,7 @@ CHANGES ======= -2.0.0 - 2013-01-xx +2.0.0 - 2013-02-xx ------------------ - Fixes #13: pep8 and pyflakes are now external dependencies @@ -10,6 +10,7 @@ CHANGES - New feature: Install git and hg hooks automagically - By relying on pyflakes (0.6.1), we also fixed #45 and #35 + 1.7.0 - 2012-12-21 ------------------ @@ -20,6 +21,7 @@ CHANGES - Fix the git hook - Update pep8.py to the latest version + 1.6.2 - 2012-11-25 ------------------ @@ -34,6 +36,7 @@ CHANGES - fixed an assumption about warnings/error messages that caused an exception to be thrown when McCabe is used + 1.6 - 2012-11-16 ---------------- @@ -50,7 +53,6 @@ CHANGES - fixed crash on non-existant file (#38) - 1.5 - 2012-10-13 ---------------- @@ -98,6 +100,7 @@ CHANGES - allow three argument form of raise - now uses setuptools if available, for 'develop' command + 1.0 - 2011-11-29 ---------------- @@ -111,6 +114,7 @@ CHANGES - update pep8 version to 0.6.1 - mccabe check: gracefully handle compile failure + 0.8 - 2011-02-27 ---------------- diff --git a/setup.py b/setup.py index 803c132..3c774da 100644 --- a/setup.py +++ b/setup.py @@ -33,8 +33,8 @@ setup( install_requires=[ "setuptools", "pyflakes >= 0.6.1", - "pep8 >= 1.4.2", - "mccabe >= 0.2a0", + "pep8 >= 1.4.3", + "mccabe >= 0.2", ], entry_points={ 'distutils.commands': ['flake8 = flake8.main:Flake8Command'], From 22040891211d55f2a315dc0886ddf73962b2a556 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Fri, 22 Feb 2013 10:22:10 +0100 Subject: [PATCH 37/41] Obsolete __future__ import --- flake8/util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/flake8/util.py b/flake8/util.py index 771cca0..7e6b37b 100644 --- a/flake8/util.py +++ b/flake8/util.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import with_statement __all__ = ['ast', 'iter_child_nodes', 'OrderedSet'] From 47bf8887816bbef6ac04ff4b95829b8c0bfaac64 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Fri, 22 Feb 2013 10:35:11 +0100 Subject: [PATCH 38/41] Review the content of the README --- README.rst | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/README.rst b/README.rst index 912ba17..21267f4 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ It also adds a few features: - lines that contain a "# noqa" comment at the end will not issue warnings. - a Git and a Mercurial hook. - a McCabe complexity checker. -- extendable through ``flake8.extension`` entry points +- extendable through ``flake8.extension`` entry points. QuickStart @@ -29,27 +29,25 @@ QuickStart To run flake8 just invoke it against any directory or Python module:: $ flake8 coolproject - coolproject/mod.py:1027: local variable 'errors' is assigned to but never used - coolproject/mod.py:97: 'shutil' imported but unused - coolproject/mod.py:729: redefinition of function 'readlines' from line 723 - coolproject/mod.py:1028: local variable 'errors' is assigned to but never used + coolproject/mod.py:97:1: F401 'shutil' imported but unused coolproject/mod.py:625:17: E225 missing whitespace around operato + coolproject/mod.py:729: F811 redefinition of function 'readlines' from line 723 + coolproject/mod.py:1028: F841 local variable 'errors' is assigned to but never used -The output of PyFlakes *and* pep8 (and the optional plugins) are merged +The outputs of PyFlakes *and* pep8 (and the optional plugins) are merged and returned. flake8 offers an extra option: --max-complexity, which will emit a warning if the McCabe complexity of a function is higher than the value. By default it's deactivated:: - $ flake8 --max-complexity 12 flake8 - coolproject/mod.py:97: 'shutil' imported but unused - coolproject/mod.py:729: redefinition of function 'readlines' from line 723 - coolproject/mod.py:1028: local variable 'errors' is assigned to but never used + $ flake8 --max-complexity 12 coolproject + coolproject/mod.py:97:1: F401 'shutil' imported but unused coolproject/mod.py:625:17: E225 missing whitespace around operator - coolproject/mod.py:452:1: 'missing_whitespace_around_operator' is too complex (18) - coolproject/mod.py:939:1: 'Checker.check_all' is too complex (12) - coolproject/mod.py:1204:1: 'selftest' is too complex (14) + coolproject/mod.py:729:1: F811 redefinition of unused 'readlines' from line 723 + coolproject/mod.py:939:1: C901 'Checker.check_all' is too complex (12) + coolproject/mod.py:1028:1: F841 local variable 'errors' is assigned to but never used + coolproject/mod.py:1204:1: C901 'selftest' is too complex (14) This feature is quite useful to detect over-complex code. According to McCabe, anything that goes beyond 10 is too complex. @@ -202,9 +200,9 @@ A list of the known prefixes is published below: `_ - ``F***``: PyFlakes codes (see below) - ``C9**``: McCabe complexity plugin `mccabe - `_ -- ``N8**``: Naming Conventions plugin `flint-naming - `_ + `_ +- ``N8**``: Naming Conventions plugin `pep8-naming + `_ (planned) The original PyFlakes does not provide error codes. Flake8 patches the From b13ec0acef4f3eb905ad1901469710cbfd87b6ab Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Fri, 22 Feb 2013 10:36:51 +0100 Subject: [PATCH 39/41] Typo in the sample output --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 21267f4..206bc50 100644 --- a/README.rst +++ b/README.rst @@ -31,8 +31,8 @@ To run flake8 just invoke it against any directory or Python module:: $ flake8 coolproject coolproject/mod.py:97:1: F401 'shutil' imported but unused coolproject/mod.py:625:17: E225 missing whitespace around operato - coolproject/mod.py:729: F811 redefinition of function 'readlines' from line 723 - coolproject/mod.py:1028: F841 local variable 'errors' is assigned to but never used + coolproject/mod.py:729:1: F811 redefinition of function 'readlines' from line 723 + coolproject/mod.py:1028:1: F841 local variable 'errors' is assigned to but never used The outputs of PyFlakes *and* pep8 (and the optional plugins) are merged and returned. From 6a984fdfdd31410fb3b9818a752a9b10f4c35818 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Fri, 22 Feb 2013 12:57:05 +0100 Subject: [PATCH 40/41] Plugin pep8-naming is released --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 206bc50..cd5387f 100644 --- a/README.rst +++ b/README.rst @@ -202,7 +202,7 @@ A list of the known prefixes is published below: - ``C9**``: McCabe complexity plugin `mccabe `_ - ``N8**``: Naming Conventions plugin `pep8-naming - `_ (planned) + `_ The original PyFlakes does not provide error codes. Flake8 patches the From 2db51c22a7095c3046dd2ba5c76911d5efc29e2f Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Fri, 22 Feb 2013 14:41:32 +0100 Subject: [PATCH 41/41] Typos in the changelog --- CHANGES.rst | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 473c9a7..c144f88 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,8 +14,8 @@ CHANGES 1.7.0 - 2012-12-21 ------------------ -- Fixes part of #35: Exception for no WITHITEM being an attribute of Checker - for python 3.3 +- Fixes part of #35: Exception for no WITHITEM being an attribute of Checker + for Python 3.3 - Support stdin - Incorporate @phd's builtins pull request - Fix the git hook @@ -31,26 +31,26 @@ CHANGES 1.6.1 - 2012-11-24 ------------------ -- fixed the mercurial hook, a change from a previous patch was not properly +- fixed the mercurial hook, a change from a previous patch was not properly applied -- fixed an assumption about warnings/error messages that caused an exception +- fixed an assumption about warnings/error messages that caused an exception to be thrown when McCabe is used 1.6 - 2012-11-16 ---------------- -- changed the signatures of the ``check_file`` function in flake8/run.py, +- changed the signatures of the ``check_file`` function in flake8/run.py, ``skip_warning`` in flake8/util.py and the ``check``, ``checkPath`` functions in flake8/pyflakes.py. - fix ``--exclude`` and ``--ignore`` command flags (#14, #19) -- fix the git hook that wasn't catching files not already added to the index +- fix the git hook that wasn't catching files not already added to the index (#29) -- pre-emptively includes the addition to pep8 to ignore certain lines. Add ``# - nopep8`` to the end of a line to ignore it. (#37) +- pre-emptively includes the addition to pep8 to ignore certain lines. + Add ``# nopep8`` to the end of a line to ignore it. (#37) - ``check_file`` can now be used without any special prior setup (#21) - unpacking exceptions will no longer cause an exception (#20) -- fixed crash on non-existant file (#38) +- fixed crash on non-existent file (#38) 1.5 - 2012-10-13 @@ -87,7 +87,7 @@ CHANGES ---------------- - added a git hook -- now python 3 compatible +- now Python 3 compatible - mccabe and pyflakes have warning codes like pep8 now @@ -125,9 +125,9 @@ CHANGES 0.7 - 2010-02-18 ---------------- -- Fix pep8 intialization when run through Hg -- Make pep8 short options work when run throug the command line -- skip duplicates when controlling files via Hg +- Fix pep8 initialization when run through Hg +- Make pep8 short options work when run through the command line +- Skip duplicates when controlling files via Hg 0.6 - 2010-02-15