diff --git a/README.rst b/README.rst index 8872cfd..09f2e02 100644 --- a/README.rst +++ b/README.rst @@ -67,6 +67,8 @@ These error codes are emitted: +------+-------------------------------------------------------+ | N817 | camelcase imported as acronym | +------+-------------------------------------------------------+ +| N818 | error suffix in exception names | ++------+-------------------------------------------------------+ Options ------- diff --git a/run_tests.py b/run_tests.py index 289af23..64d80b5 100644 --- a/run_tests.py +++ b/run_tests.py @@ -75,6 +75,10 @@ class OptionsManager(optparse.OptionParser): def __init__(self, *args, **kwargs): optparse.OptionParser.__init__(self, *args, **kwargs) self.config_options = [] + self.ignore = [] + + def extend_default_ignore(self, new_ignores): + self.ignore += new_ignores def parse_options(checker, options): @@ -85,6 +89,7 @@ def parse_options(checker, options): options_manager.add_option('--ignore', default=[]) options_manager.add_option('--extend-ignore', default=[]) options_manager.add_option('--enable-extensions', default=[]) + options_manager.add_option('--extended-default-ignore', default=[]) checker.add_options(options_manager) processed_options, _ = options_manager.parse_args(options) checker.parse_options(processed_options) diff --git a/setup.py b/setup.py index 1e94ea8..8fd4807 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ def run_tests(self): license='Expat license', package_dir={'': 'src'}, py_modules=['pep8ext_naming'], - install_requires=['flake8_polyfill>=1.0.2,<2'], + install_requires=['flake8>=3.9.1', 'flake8_polyfill>=1.0.2,<2'], zip_safe=False, entry_points={ 'flake8.extension': [ diff --git a/src/pep8ext_naming.py b/src/pep8ext_naming.py index 51d60f5..2037435 100644 --- a/src/pep8ext_naming.py +++ b/src/pep8ext_naming.py @@ -167,6 +167,7 @@ def add_options(cls, parser): help='List of method decorators pep8-naming plugin ' 'should consider staticmethods (Defaults to ' '%default)') + parser.extend_default_ignore(['N818']) @classmethod def parse_options(cls, options): @@ -291,6 +292,25 @@ class ClassNameCheck(BaseASTCheck): Classes for internal use have a leading underscore in addition. """ N801 = "class name '{name}' should use CapWords convention" + N818 = "exception name '{name}' should be named with an Error suffix" + + def get_class_def(self, name, parents): + for parent in parents: + for definition in parent.body: + is_class_definition = isinstance(definition, ast.ClassDef) + if is_class_definition and definition.name == name: + return definition + + def superclass_names(self, name, parents): + class_ids = set() + class_def = self.get_class_def(name, parents) + if not class_def: + return class_ids + for base in class_def.bases: + if hasattr(base, "id"): + class_ids.add(base.id) + class_ids.update(self.superclass_names(base.id, parents)) + return class_ids def visit_classdef(self, node, parents, ignore=None): name = node.name @@ -299,6 +319,9 @@ def visit_classdef(self, node, parents, ignore=None): name = name.strip('_') if not name[:1].isupper() or '_' in name: yield self.err(node, 'N801', name=name) + superclasses = self.superclass_names(name, parents) + if "Exception" in superclasses and not name.endswith("Error"): + yield self.err(node, 'N818', name=name) class FunctionNameCheck(BaseASTCheck): diff --git a/testsuite/N818.py b/testsuite/N818.py new file mode 100644 index 0000000..1d0ae10 --- /dev/null +++ b/testsuite/N818.py @@ -0,0 +1,30 @@ +#: Okay +class ActionError(Exception): + pass +#: N818 +class ActionClass(Exception): + pass +#: Okay +class ActionError(Exception): + pass +class DeepActionError(ActionError): + pass +#: N818 +class ActionError(Exception): + pass +class DeepActionClass(ActionError): + pass +#: Okay +class MixinError(Exception): + pass +class Mixin: + pass +class MixinActionError(Mixin, MixinError): + pass +#: N818 +class MixinError(Exception): + pass +class Mixin: + pass +class MixinActionClass(Mixin, MixinError): + pass diff --git a/tox.ini b/tox.ini index dc57fd6..77b32cf 100644 --- a/tox.ini +++ b/tox.ini @@ -17,7 +17,7 @@ commands = python run_tests.py [testenv:py27-flake8] deps = - flake8 + flake8 >= 3.9.1 commands = flake8 {posargs} src/pep8ext_naming.py python setup.py check --restructuredtext @@ -25,7 +25,7 @@ commands = [testenv:py39-flake8] basepython = python3.9 deps = - flake8 + flake8 >= 3.9.1 commands = flake8 {posargs} src/pep8ext_naming.py python setup.py check --restructuredtext