From eafc91ae6ad5c05b3e036733d73a0edac9070192 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 2 Feb 2016 20:48:26 -0600 Subject: [PATCH] Add handling and decision making for select and ignore --- flake8/style_guide.py | 106 ++++++++++++++++++++++++++- setup.cfg | 4 ++ setup.py | 17 +++-- tests/unit/test_style_guide.py | 128 +++++++++++++++++++++++++++++++++ 4 files changed, 249 insertions(+), 6 deletions(-) create mode 100644 tests/unit/test_style_guide.py diff --git a/flake8/style_guide.py b/flake8/style_guide.py index ec000b7..ef1cbae 100644 --- a/flake8/style_guide.py +++ b/flake8/style_guide.py @@ -1,4 +1,30 @@ """Implementation of the StyleGuide used by Flake8.""" +import enum + +__all__ = ( + 'StyleGuide', +) + + +class Selected(enum.Enum): + """Enum representing an explicitly or implicitly selected code.""" + + Explicitly = 'explicitly selected' + Implicitly = 'implicitly selected' + + +class Ignored(enum.Enum): + """Enum representing an explicitly or implicitly ignored code.""" + + Explicitly = 'explicitly ignored' + Implicitly = 'implicitly ignored' + + +class Decision(enum.Enum): + """Enum representing whether a code should be ignored or selected.""" + + Ignored = 'ignored error' + Selected = 'selected error' class StyleGuide(object): @@ -10,7 +36,85 @@ class StyleGuide(object): .. todo:: Add parameter documentation. """ - pass + self.options = options + self.arguments = arguments + self.checkers = checker_plugins + self.listeners = listening_plugins + self.formatters = formatting_plugins + self._selected = tuple(options.select) + self._ignored = tuple(options.ignore) + self._decision_cache = {} + + def is_user_selected(self, code): + """Determine if the code has been selected by the user. + + :param str code: + The code for the check that has been run. + :returns: + Selected.Implicitly if the selected list is empty, + Selected.Explicitly if the selected list is not empty and a match + was found, + Ignored.Implicitly if the selected list is not empty but no match + was found. + """ + if not self._selected: + return Selected.Implicitly + + if code.startswith(self._selected): + return Selected.Explicitly + + return Ignored.Implicitly + + def is_user_ignored(self, code): + """Determine if the code has been ignored by the user. + + :param str code: + The code for the check that has been run. + :returns: + Selected.Implicitly if the ignored list is empty, + Ignored.Explicitly if the ignored list is not empty and a match was + found, + Selected.Implicitly if the ignored list is not empty but no match + was found. + """ + if self._ignored and code.startswith(self._ignored): + return Ignored.Explicitly + + return Selected.Implicitly + + def _decision_for(self, code): + startswith = code.startswith + selected = sorted([s for s in self._selected if startswith(s)])[0] + ignored = sorted([i for i in self._ignored if startswith(i)])[0] + + if selected.startswith(ignored): + return Decision.Selected + return Decision.Ignored + + def should_report_error(self, code): + """Determine if the error code should be reported or ignored. + + :param str code: + The code for the check that has been run. + """ + decision = self._decision_cache.get(code) + if decision is None: + selected = self.is_user_selected(code) + ignored = self.is_user_ignored(code) + + if ((selected is Selected.Explicitly or + selected is Selected.Implicitly) and + ignored is Selected.Implicitly): + decision = Decision.Selected + elif (selected is Selected.Explicitly and + ignored is Ignored.Explicitly): + decision = self._decision_for(code) + elif (selected is Ignored.Implicitly or + ignored is Ignored.Explicitly): + decision = Decision.Ignored + + self._decision_cache[code] = decision + return decision # Should separate style guide logic from code that runs checks diff --git a/setup.cfg b/setup.cfg index f05f933..e96761b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,7 @@ test=pytest [bdist_wheel] universal=1 + +[metadata] +requires-dist = + enum34; python_version<"3.4" diff --git a/setup.py b/setup.py index 93e4fdb..0b90cf7 100644 --- a/setup.py +++ b/setup.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from __future__ import with_statement import setuptools +import sys import flake8 @@ -25,6 +26,16 @@ if mock is None: tests_require += ['mock'] +requires = [ + "pyflakes >= 0.8.1, < 1.1", + "pep8 >= 1.5.7, != 1.6.0, != 1.6.1, != 1.6.2", + # "mccabe >= 0.2.1, < 0.4", +] + +if sys.version_info < (3, 4): + requires.append("enum34") + + def get_long_description(): """Generate a long description from the README and CHANGES files.""" descr = [] @@ -51,11 +62,7 @@ setuptools.setup( "flake8.options", "flake8.plugins", ], - install_requires=[ - "pyflakes >= 0.8.1, < 1.1", - "pep8 >= 1.5.7, != 1.6.0, != 1.6.1, != 1.6.2", - # "mccabe >= 0.2.1, < 0.4", - ], + install_requires=requires, entry_points={ 'distutils.commands': ['flake8 = flake8.main:Flake8Command'], 'console_scripts': ['flake8 = flake8.main.cli:main'], diff --git a/tests/unit/test_style_guide.py b/tests/unit/test_style_guide.py new file mode 100644 index 0000000..51e9f61 --- /dev/null +++ b/tests/unit/test_style_guide.py @@ -0,0 +1,128 @@ +"""Tests for the flake8.style_guide.StyleGuide class.""" +import optparse + +from flake8 import style_guide + +import pytest + + +def create_options(**kwargs): + """Create and return an instance of optparse.Values.""" + kwargs.setdefault('select', []) + kwargs.setdefault('ignore', []) + return optparse.Values(kwargs) + + +@pytest.mark.parametrize('ignore_list,error_code', [ + (['E111', 'E121'], 'E111'), + (['E111', 'E121'], 'E121'), + (['E11', 'E12'], 'E121'), + (['E2', 'E12'], 'E121'), + (['E2', 'E12'], 'E211'), +]) +def test_is_user_ignored_ignores_errors(ignore_list, error_code): + """Verify we detect users explicitly ignoring an error.""" + guide = style_guide.StyleGuide(create_options(ignore=ignore_list), + arguments=[], + checker_plugins=None, + listening_plugins=None, + formatting_plugins=None) + + assert guide.is_user_ignored(error_code) is style_guide.Ignored.Explicitly + + +@pytest.mark.parametrize('ignore_list,error_code', [ + (['E111', 'E121'], 'E112'), + (['E111', 'E121'], 'E122'), + (['E11', 'E12'], 'W121'), + (['E2', 'E12'], 'E112'), + (['E2', 'E12'], 'E111'), +]) +def test_is_user_ignored_implicitly_selects_errors(ignore_list, error_code): + """Verify we detect users does not explicitly ignore an error.""" + guide = style_guide.StyleGuide(create_options(ignore=ignore_list), + arguments=[], + checker_plugins=None, + listening_plugins=None, + formatting_plugins=None) + + assert guide.is_user_ignored(error_code) is style_guide.Selected.Implicitly + + +@pytest.mark.parametrize('select_list,error_code', [ + (['E111', 'E121'], 'E111'), + (['E111', 'E121'], 'E121'), + (['E11', 'E12'], 'E121'), + (['E2', 'E12'], 'E121'), + (['E2', 'E12'], 'E211'), +]) +def test_is_user_selected_selects_errors(select_list, error_code): + """Verify we detect users explicitly selecting an error.""" + guide = style_guide.StyleGuide(create_options(select=select_list), + arguments=[], + checker_plugins=None, + listening_plugins=None, + formatting_plugins=None) + + assert (guide.is_user_selected(error_code) is + style_guide.Selected.Explicitly) + + +def test_is_user_selected_implicitly_selects_errors(): + """Verify we detect users implicitly selecting an error.""" + select_list = [] + error_code = 'E121' + guide = style_guide.StyleGuide(create_options(select=select_list), + arguments=[], + checker_plugins=None, + listening_plugins=None, + formatting_plugins=None) + + assert (guide.is_user_selected(error_code) is + style_guide.Selected.Implicitly) + + +@pytest.mark.parametrize('select_list,error_code', [ + (['E111', 'E121'], 'E112'), + (['E111', 'E121'], 'E122'), + (['E11', 'E12'], 'E132'), + (['E2', 'E12'], 'E321'), + (['E2', 'E12'], 'E410'), +]) +def test_is_user_selected_excludes_errors(select_list, error_code): + """Verify we detect users implicitly excludes an error.""" + guide = style_guide.StyleGuide(create_options(select=select_list), + arguments=[], + checker_plugins=None, + listening_plugins=None, + formatting_plugins=None) + + assert guide.is_user_selected(error_code) is style_guide.Ignored.Implicitly + + +@pytest.mark.parametrize('select_list,ignore_list,error_code,expected', [ + (['E111', 'E121'], [], 'E111', style_guide.Decision.Selected), + (['E111', 'E121'], [], 'E112', style_guide.Decision.Ignored), + (['E111', 'E121'], [], 'E121', style_guide.Decision.Selected), + (['E111', 'E121'], [], 'E122', style_guide.Decision.Ignored), + (['E11', 'E12'], [], 'E132', style_guide.Decision.Ignored), + (['E2', 'E12'], [], 'E321', style_guide.Decision.Ignored), + (['E2', 'E12'], [], 'E410', style_guide.Decision.Ignored), + (['E11', 'E121'], ['E1'], 'E112', style_guide.Decision.Selected), + (['E111', 'E121'], ['E2'], 'E122', style_guide.Decision.Ignored), + (['E11', 'E12'], ['E13'], 'E132', style_guide.Decision.Ignored), + (['E1', 'E3'], ['E32'], 'E321', style_guide.Decision.Ignored), + ([], ['E2', 'E12'], 'E410', style_guide.Decision.Selected), + (['E4'], ['E2', 'E12', 'E41'], 'E410', style_guide.Decision.Ignored), + (['E41'], ['E2', 'E12', 'E4'], 'E410', style_guide.Decision.Selected), +]) +def test_should_report_error(select_list, ignore_list, error_code, expected): + """Verify we decide when to report an error.""" + guide = style_guide.StyleGuide(create_options(select=select_list, + ignore=ignore_list), + arguments=[], + checker_plugins=None, + listening_plugins=None, + formatting_plugins=None) + + assert guide.should_report_error(error_code) is expected