From 54e9add2eee692e86b7fa9d41d6615d8a7773a77 Mon Sep 17 00:00:00 2001 From: Michele Lacchia Date: Mon, 6 Jul 2015 11:23:26 +0200 Subject: [PATCH 1/7] feat(Flake8Checker): add Flake8 entry point Attempt for issue #76 --- radon/complexity.py | 32 ++++++++++++++++++++++++++++ radon/tests/test_complexity_utils.py | 13 +++++++++++ setup.py | 7 +++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/radon/complexity.py b/radon/complexity.py index f7cc951..35ca9e6 100644 --- a/radon/complexity.py +++ b/radon/complexity.py @@ -98,3 +98,35 @@ def cc_visit_ast(ast_node, **kwargs): the keyword arguments are directly passed to the visitor. ''' return ComplexityVisitor.from_ast(ast_node, **kwargs).blocks + + +class Flake8Checker(object): + '''Entry point for the Flake8 tool.''' + + name = 'radon' + _code = 'R701' + _error_tmpl = 'R701: %r is too complex (%d)' + max_cc = 10 + + def __init__(self, tree, filename): + self.tree = tree + + version = property(lambda self: __import__('radon').__version__) + + @classmethod + def add_options(cls, parser): # pragma: no cover + parser.add_option('--radon-max-cc', default=10, action='store', + type='int', help='Radon complexity threshold') + parser.config_options.append('radon-max-cc') + + @classmethod + def parse_options(cls, options): # pragma: no cover + cls.max_cc = options.radon_max_cc + + def run(self): + if self.max_cc < 0: + return + for block in ComplexityVisitor.from_ast(self.tree).blocks: + if block.complexity > self.max_cc: + text = self._error_tmpl % (block.name, block.complexity) + yield block.lineno, 0, text, type(self) diff --git a/radon/tests/test_complexity_utils.py b/radon/tests/test_complexity_utils.py index 42cb90d..edf6621 100644 --- a/radon/tests/test_complexity_utils.py +++ b/radon/tests/test_complexity_utils.py @@ -1,4 +1,6 @@ +import ast import operator +import unittest from paramunittest import * from radon.complexity import * from radon.visitors import Class, Function @@ -104,3 +106,14 @@ def test_add_closures(self): names = set(map(operator.attrgetter('name'), with_closures)) self.assertEqual(len(with_closures) - len(blocks), 1) self.assertTrue('f.inner' in names) + + +class TestFlake8Checker(unittest.TestCase): + + def test_run(self): + c = Flake8Checker(ast.parse(dedent(GENERAL_CASES[0][0])), 'test case') + c.max_cc = 3 + self.assertEqual(list(c.run()), [(7, 0, "R701: 'f' is too complex (4)", + type(c))]) + c.max_cc = -1 + self.assertEqual(list(c.run()), []) diff --git a/setup.py b/setup.py index 26180cc..a51d60c 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,12 @@ packages=find_packages(), tests_require=['tox'], install_requires=['mando', 'colorama'], - entry_points={'console_scripts': ['radon = radon:main']}, + entry_points={ + 'console_scripts': ['radon = radon:main'], + 'flake8.extension': [ + 'R70 = radon.complexity:Flake8Checker', + ], + }, keywords='static analysis code complexity metrics', classifiers=[ 'Development Status :: 5 - Production/Stable', From 10d5c377e37bd1ea000e525fae6d72c8585ab761 Mon Sep 17 00:00:00 2001 From: Michele Lacchia Date: Mon, 6 Jul 2015 16:16:21 +0200 Subject: [PATCH 2/7] fix(Flake8Checker): set max_cc to -1 by defaut and add --radon-no-assert Affects #76 --- radon/complexity.py | 14 +++++++++++--- radon/tests/test_complexity_utils.py | 5 +++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/radon/complexity.py b/radon/complexity.py index 35ca9e6..6219e8d 100644 --- a/radon/complexity.py +++ b/radon/complexity.py @@ -106,7 +106,8 @@ class Flake8Checker(object): name = 'radon' _code = 'R701' _error_tmpl = 'R701: %r is too complex (%d)' - max_cc = 10 + no_assert = False + max_cc = -1 def __init__(self, tree, filename): self.tree = tree @@ -115,18 +116,25 @@ def __init__(self, tree, filename): @classmethod def add_options(cls, parser): # pragma: no cover - parser.add_option('--radon-max-cc', default=10, action='store', + parser.add_option('--radon-max-cc', default=-1, action='store', type='int', help='Radon complexity threshold') + parser.add_option('--radon-no-assert', dest='no_assert', + action='store_true', default=False, + help='Radon will ignore assert statements') parser.config_options.append('radon-max-cc') + parser.config_options.append('radon-no-assert') @classmethod def parse_options(cls, options): # pragma: no cover cls.max_cc = options.radon_max_cc + cls.no_assert = options.no_assert def run(self): if self.max_cc < 0: return - for block in ComplexityVisitor.from_ast(self.tree).blocks: + visitor = ComplexityVisitor.from_ast(self.tree, + no_assert=self.no_assert) + for block in visitor.blocks: if block.complexity > self.max_cc: text = self._error_tmpl % (block.name, block.complexity) yield block.lineno, 0, text, type(self) diff --git a/radon/tests/test_complexity_utils.py b/radon/tests/test_complexity_utils.py index edf6621..7ed1d13 100644 --- a/radon/tests/test_complexity_utils.py +++ b/radon/tests/test_complexity_utils.py @@ -112,8 +112,9 @@ class TestFlake8Checker(unittest.TestCase): def test_run(self): c = Flake8Checker(ast.parse(dedent(GENERAL_CASES[0][0])), 'test case') + self.assertEqual(c.max_cc, -1) + self.assertEqual(c.no_assert, False) + self.assertEqual(list(c.run()), []) c.max_cc = 3 self.assertEqual(list(c.run()), [(7, 0, "R701: 'f' is too complex (4)", type(c))]) - c.max_cc = -1 - self.assertEqual(list(c.run()), []) From 10e0b0dc64fe3b288eb452716e98c23b337f9a33 Mon Sep 17 00:00:00 2001 From: Michele Lacchia Date: Mon, 6 Jul 2015 17:16:08 +0200 Subject: [PATCH 3/7] docs(intro): fix link and add Arie blog post --- docs/intro.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/intro.rst b/docs/intro.rst index 43d12d1..ca4ea5d 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -131,5 +131,9 @@ Further Reading `_, `postprint `_) -3. `Maintainability Index Range and Meaning `_. Code Analysis Team Blog, blogs.msdn, 20 November 2007. + +4. Arie van Deursen, `Think Twice Before Using the “Maintainability Index” + `_. From 3c64a03044e51fb8cf46bad2b20229ec04bfd269 Mon Sep 17 00:00:00 2001 From: Michele Lacchia Date: Wed, 8 Jul 2015 10:03:48 +0200 Subject: [PATCH 4/7] style(Flake8Checker): add docstrings --- radon/complexity.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/radon/complexity.py b/radon/complexity.py index 6219e8d..8adfc33 100644 --- a/radon/complexity.py +++ b/radon/complexity.py @@ -110,12 +110,14 @@ class Flake8Checker(object): max_cc = -1 def __init__(self, tree, filename): + '''Accept the AST tree and a filename (unused).''' self.tree = tree version = property(lambda self: __import__('radon').__version__) @classmethod def add_options(cls, parser): # pragma: no cover + '''Add custom options to the global parser.''' parser.add_option('--radon-max-cc', default=-1, action='store', type='int', help='Radon complexity threshold') parser.add_option('--radon-no-assert', dest='no_assert', @@ -126,10 +128,12 @@ def add_options(cls, parser): # pragma: no cover @classmethod def parse_options(cls, options): # pragma: no cover + '''Save actual options as class attributes.''' cls.max_cc = options.radon_max_cc cls.no_assert = options.no_assert def run(self): + '''Run the ComplexityVisitor over the AST tree.''' if self.max_cc < 0: return visitor = ComplexityVisitor.from_ast(self.tree, From 160890cf612404a17b30384b9d1711a1c66cc6a0 Mon Sep 17 00:00:00 2001 From: Michele Lacchia Date: Thu, 9 Jul 2015 07:58:31 +0200 Subject: [PATCH 5/7] fix(Flake8Checker): activate checker on any options, as per #76 --- radon/complexity.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/radon/complexity.py b/radon/complexity.py index 8adfc33..3937eed 100644 --- a/radon/complexity.py +++ b/radon/complexity.py @@ -135,7 +135,9 @@ def parse_options(cls, options): # pragma: no cover def run(self): '''Run the ComplexityVisitor over the AST tree.''' if self.max_cc < 0: - return + if not self.no_assert: + return + self.max_cc = 10 visitor = ComplexityVisitor.from_ast(self.tree, no_assert=self.no_assert) for block in visitor.blocks: From 6dde914406a73a490a8337aba34e862375aa2e2f Mon Sep 17 00:00:00 2001 From: Michele Lacchia Date: Thu, 9 Jul 2015 22:13:04 +0200 Subject: [PATCH 6/7] docs(flake8): add documentation for the flake8 plugin Related: #76 --- docs/flake8.rst | 24 ++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 25 insertions(+) create mode 100644 docs/flake8.rst diff --git a/docs/flake8.rst b/docs/flake8.rst new file mode 100644 index 0000000..e36724b --- /dev/null +++ b/docs/flake8.rst @@ -0,0 +1,24 @@ +Flake8 plugin +============= + +.. program:: flake8 + +Radon exposes a plugin for the Flake8 tool. In order to use it you will have to +install both `radon` and `flake8`. + +To enable the Radon checker, you will have to supply *at least* one of the +following options: + +.. option:: --radon-max-cc + + Set the cyclomatic complexity threshold. The default value is `10`. Blocks + with a greater complexity will be reported by the tool. + +.. option:: --radon-no-assert + + Instruct radon not to count `assert` statements towards cyclomatic + complexity. The default behaviour is the opposite. + + +For more information visit the `Flake8 documentation +`_. diff --git a/docs/index.rst b/docs/index.rst index 9b3b437..ccf1da9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -50,6 +50,7 @@ Contents: intro commandline + flake8 api changelog From 81a8d6e0015c79adae5318a33ebb8d547d47b70a Mon Sep 17 00:00:00 2001 From: Michele Lacchia Date: Thu, 9 Jul 2015 22:18:51 +0200 Subject: [PATCH 7/7] chore(release): bump to 1.2.2 --- CHANGELOG | 10 ++++++++-- radon/__init__.py | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 385cd24..9356233 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +1.2.2 (Jul 09, 2015) +-------------------- + +- Add plugin for flake8 tool: #76. + 1.2.1 (May 07, 2015) -------------------- @@ -6,8 +11,9 @@ 1.2 (Jan 16, 2015) ------------------ -- Breaking change regarding to CC of lambda functions and nested functions: #68 -- Fix the bug that caused classes with only one method have a CC of 2: #70 +- **Backwards incompatible** change regarding to CC of lambda functions and + nested functions: #68. +- Fix the bug that caused classes with only one method have a CC of 2: #70. 1.1 (Sep 6, 2014) ----------------- diff --git a/radon/__init__.py b/radon/__init__.py index faa9706..b0dd65e 100644 --- a/radon/__init__.py +++ b/radon/__init__.py @@ -1,7 +1,7 @@ '''This module contains the main() function, which is the entry point for the command line interface.''' -__version__ = '1.2.1' +__version__ = '1.2.2' def main():