From 93c580cb768617bef08abdb5b614443d9f62ba05 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Mon, 10 Sep 2018 16:23:23 +0300 Subject: [PATCH] Version 0.0.10 prerelease (#111) --- CHANGELOG.md | 26 +++- README.md | 24 ++++ docs/_pages/errors.rst | 3 + pyproject.toml | 4 +- setup.cfg | 2 +- tests/conftest.py | 13 +- tests/test_errors.py | 4 +- tests/test_version.py | 7 +- tests/test_visitors/conftest.py | 13 +- .../test_counts/test_method_counts.py | 14 +- .../test_counts/test_module_counts.py | 14 +- .../test_function/test_elifs.py | 14 +- .../test_function/test_expressions.py | 14 +- .../test_function/test_local_variables.py | 14 +- .../test_function/test_returns.py | 18 ++- .../test_nested/test_nested_classes.py | 26 ++-- .../test_nested/test_nested_functions.py | 32 ++--- .../test_complexity/test_offset_visitor.py | 14 +- .../test_wrong_class/test_base_class.py | 34 +++-- .../test_wrong_class/test_magic_methods.py | 14 +- .../test_wrong_class/test_staticmethod.py | 14 +- .../test_wrong_contents/test_empty_init.py | 72 ++++++++++ .../test_wrong_contents/test_empty_modules.py | 28 ++++ .../test_wrong_function_calls.py | 35 +++-- .../test_dotted_raw_import.py | 39 ++---- .../test_wrong_import/test_future_imports.py | 34 +++-- .../test_wrong_import/test_nested_imports.py | 25 ++-- .../test_relative_imports.py | 51 ++++++++ .../test_same_alias_import.py | 23 ++-- .../test_wrong_keyword/test_bare_raise.py | 20 +-- .../test_wrong_keyword/test_del.py | 8 +- .../test_wrong_keyword/test_global.py | 8 +- .../test_wrong_keyword/test_pass.py | 17 ++- .../test_raise_notimplemented.py | 20 ++- .../test_wrong_name/test_class_attributes.py | 26 ++-- .../test_function_args_names.py | 26 ++-- .../test_wrong_name/test_function_names.py | 26 ++-- .../test_wrong_name/test_import_alias.py | 26 ++-- .../test_wrong_name/test_module_metadata.py | 20 +-- .../test_wrong_name/test_variable_names.py | 32 ++--- .../{ => test_ast}/test_wrong_string.py | 14 +- tests/test_visitors/test_base.py | 39 ++++++ .../test_module_magic_name.py | 31 +++++ .../test_module_name.py | 32 +++++ .../test_module_name_length.py | 31 +++++ .../test_relative_imports.py | 62 --------- wemake_python_styleguide/checker.py | 61 ++++++--- wemake_python_styleguide/constants.py | 30 ++++- wemake_python_styleguide/errors/base.py | 58 +++++---- wemake_python_styleguide/errors/classes.py | 32 +++-- wemake_python_styleguide/errors/complexity.py | 94 ++++++++----- wemake_python_styleguide/errors/general.py | 123 +++++++++++++----- wemake_python_styleguide/errors/imports.py | 48 ++++--- wemake_python_styleguide/errors/modules.py | 80 ++++++++++++ wemake_python_styleguide/logics/filenames.py | 78 +++++++++++ wemake_python_styleguide/logics/variables.py | 4 +- wemake_python_styleguide/options/config.py | 76 +++++------ wemake_python_styleguide/options/defaults.py | 12 +- wemake_python_styleguide/types.py | 9 +- wemake_python_styleguide/version.py | 4 +- .../visitors/{base => ast}/__init__.py | 0 .../visitors/{ => ast}/complexity/__init__.py | 0 .../visitors/{ => ast}/complexity/counts.py | 12 +- .../visitors/{ => ast}/complexity/function.py | 20 +-- .../visitors/{ => ast}/complexity/nested.py | 8 +- .../visitors/{ => ast}/complexity/offset.py | 6 +- .../visitors/{ => ast}/wrong_class.py | 21 ++- .../visitors/ast/wrong_contents.py | 57 ++++++++ .../visitors/{ => ast}/wrong_function_call.py | 4 +- .../visitors/{ => ast}/wrong_import.py | 18 +-- .../visitors/{ => ast}/wrong_keyword.py | 10 +- .../visitors/{ => ast}/wrong_name.py | 43 +++--- .../visitors/{ => ast}/wrong_string.py | 4 +- wemake_python_styleguide/visitors/base.py | 79 +++++++++++ .../visitors/base/visitor.py | 32 ----- .../visitors/filenames/__init__.py | 1 + .../visitors/filenames/wrong_module_name.py | 56 ++++++++ 77 files changed, 1447 insertions(+), 696 deletions(-) rename tests/test_visitors/{ => test_ast}/test_complexity/test_counts/test_method_counts.py (77%) rename tests/test_visitors/{ => test_ast}/test_complexity/test_counts/test_module_counts.py (74%) rename tests/test_visitors/{ => test_ast}/test_complexity/test_function/test_elifs.py (75%) rename tests/test_visitors/{ => test_ast}/test_complexity/test_function/test_expressions.py (71%) rename tests/test_visitors/{ => test_ast}/test_complexity/test_function/test_local_variables.py (82%) rename tests/test_visitors/{ => test_ast}/test_complexity/test_function/test_returns.py (66%) rename tests/test_visitors/{ => test_ast}/test_complexity/test_nested/test_nested_classes.py (74%) rename tests/test_visitors/{ => test_ast}/test_complexity/test_nested/test_nested_functions.py (72%) rename tests/test_visitors/{ => test_ast}/test_complexity/test_offset_visitor.py (84%) rename tests/test_visitors/{ => test_ast}/test_wrong_class/test_base_class.py (60%) rename tests/test_visitors/{ => test_ast}/test_wrong_class/test_magic_methods.py (72%) rename tests/test_visitors/{ => test_ast}/test_wrong_class/test_staticmethod.py (70%) create mode 100644 tests/test_visitors/test_ast/test_wrong_contents/test_empty_init.py create mode 100644 tests/test_visitors/test_ast/test_wrong_contents/test_empty_modules.py rename tests/test_visitors/{ => test_ast}/test_wrong_function_call/test_wrong_function_calls.py (64%) rename tests/test_visitors/{ => test_ast}/test_wrong_import/test_dotted_raw_import.py (67%) rename tests/test_visitors/{ => test_ast}/test_wrong_import/test_future_imports.py (62%) rename tests/test_visitors/{ => test_ast}/test_wrong_import/test_nested_imports.py (73%) create mode 100644 tests/test_visitors/test_ast/test_wrong_import/test_relative_imports.py rename tests/test_visitors/{ => test_ast}/test_wrong_import/test_same_alias_import.py (66%) rename tests/test_visitors/{ => test_ast}/test_wrong_keyword/test_bare_raise.py (70%) rename tests/test_visitors/{ => test_ast}/test_wrong_keyword/test_del.py (69%) rename tests/test_visitors/{ => test_ast}/test_wrong_keyword/test_global.py (73%) rename tests/test_visitors/{ => test_ast}/test_wrong_keyword/test_pass.py (63%) rename tests/test_visitors/{ => test_ast}/test_wrong_keyword/test_raise_notimplemented.py (65%) rename tests/test_visitors/{ => test_ast}/test_wrong_name/test_class_attributes.py (76%) rename tests/test_visitors/{ => test_ast}/test_wrong_name/test_function_args_names.py (84%) rename tests/test_visitors/{ => test_ast}/test_wrong_name/test_function_names.py (75%) rename tests/test_visitors/{ => test_ast}/test_wrong_name/test_import_alias.py (75%) rename tests/test_visitors/{ => test_ast}/test_wrong_name/test_module_metadata.py (73%) rename tests/test_visitors/{ => test_ast}/test_wrong_name/test_variable_names.py (78%) rename tests/test_visitors/{ => test_ast}/test_wrong_string.py (75%) create mode 100644 tests/test_visitors/test_base.py create mode 100644 tests/test_visitors/test_filenames/test_wrong_module_name/test_module_magic_name.py create mode 100644 tests/test_visitors/test_filenames/test_wrong_module_name/test_module_name.py create mode 100644 tests/test_visitors/test_filenames/test_wrong_module_name/test_module_name_length.py delete mode 100644 tests/test_visitors/test_wrong_import/test_relative_imports.py create mode 100644 wemake_python_styleguide/errors/modules.py create mode 100644 wemake_python_styleguide/logics/filenames.py rename wemake_python_styleguide/visitors/{base => ast}/__init__.py (100%) rename wemake_python_styleguide/visitors/{ => ast}/complexity/__init__.py (100%) rename wemake_python_styleguide/visitors/{ => ast}/complexity/counts.py (87%) rename wemake_python_styleguide/visitors/{ => ast}/complexity/function.py (89%) rename wemake_python_styleguide/visitors/{ => ast}/complexity/nested.py (89%) rename wemake_python_styleguide/visitors/{ => ast}/complexity/offset.py (86%) rename wemake_python_styleguide/visitors/{ => ast}/wrong_class.py (67%) create mode 100644 wemake_python_styleguide/visitors/ast/wrong_contents.py rename wemake_python_styleguide/visitors/{ => ast}/wrong_function_call.py (87%) rename wemake_python_styleguide/visitors/{ => ast}/wrong_import.py (83%) rename wemake_python_styleguide/visitors/{ => ast}/wrong_keyword.py (88%) rename wemake_python_styleguide/visitors/{ => ast}/wrong_name.py (77%) rename wemake_python_styleguide/visitors/{ => ast}/wrong_string.py (72%) create mode 100644 wemake_python_styleguide/visitors/base.py delete mode 100644 wemake_python_styleguide/visitors/base/visitor.py create mode 100644 wemake_python_styleguide/visitors/filenames/__init__.py create mode 100644 wemake_python_styleguide/visitors/filenames/wrong_module_name.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b18720d00..3b2045835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,30 @@ We follow Semantic Versions since the `0.1.0` release. +## Version 0.0.10 aka The Module Reaper + +### Features + +- Adds `WrongModuleNameViolation`, `WrongModuleMagicNameViolation`, + and `TooShortModuleNameViolation` +- Adds `--min-module-name-length` config option +- Adds a blacklist of module names +- Adds `InitModuleHasLogicsViolation` +- Adds `EmptyModuleViolation` +- Adds a whitelist of magic module names + +### Bugfixes + +- Fixes `Option` class to have have incorrect `type` field, now using strings +- Fixes that `WrongStringVisitor` was not activated + +### Misc + +- Improved typing support +- Now each error has a link to the corresponding constant (if any) +- Improved docs with links to the corresponding configuration flags + + ## Version 0.0.9 This is just a supporting release. @@ -58,7 +82,7 @@ There are no new features introduced. - Refactored how errors are defined - Now each check has strict `Raises:` policy which lists all possible errors that this check can find and raise -- Changed how visiters are initialized in tests +- Changed how visitors are initialized in tests - Tests now cover nested classes' explicit bases - Tests now cover nested classes and functions `noqa` comment diff --git a/README.md b/README.md index 202dd08cf..4175e6eac 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,30 @@ Welcome to the most opinionated linter ever. `wemake-python-styleguide` is actually just a `flake8` plugin. The main goal of this tool is to make our `python` code consistent. +``` +The Zen of Python, by Tim Peters + +Beautiful is better than ugly. +Explicit is better than implicit. +Simple is better than complex. +Complex is better than complicated. +Flat is better than nested. +Sparse is better than dense. +Readability counts. +Special cases aren't special enough to break the rules. +Although practicality beats purity. +Errors should never pass silently. +Unless explicitly silenced. +In the face of ambiguity, refuse the temptation to guess. +There should be one-- and preferably only one --obvious way to do it. +Although that way may not be obvious at first unless you're Dutch. +Now is better than never. +Although never is often better than *right* now. +If the implementation is hard to explain, it's a bad idea. +If the implementation is easy to explain, it may be a good idea. +Namespaces are one honking great idea -- let's do more of those! +``` + ## Installation diff --git a/docs/_pages/errors.rst b/docs/_pages/errors.rst index 4a5c9206e..0b98ea69f 100644 --- a/docs/_pages/errors.rst +++ b/docs/_pages/errors.rst @@ -15,3 +15,6 @@ Errors .. automodule:: wemake_python_styleguide.errors.complexity :members: + +.. automodule:: wemake_python_styleguide.errors.modules + :members: diff --git a/pyproject.toml b/pyproject.toml index 35ceef094..5d09f7044 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "wemake-python-styleguide" -version = "0.0.9" -description = "Opinionated styleguide that we use in wemake.services" +version = "0.0.10" +description = "The most opinionated linter ever, used by wemake.services" license = "MIT" diff --git a/setup.cfg b/setup.cfg index 0a218a37b..be000419d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,7 +24,7 @@ accept-encodings = utf-8 [tool:pytest] flake8-ignore = # These function names are part of 3d party API: - wemake_python_styleguide/visitors/*.py N802 + wemake_python_styleguide/visitors/ast/*.py N802 # These modules should contain a lot of classes: wemake_python_styleguide/errors/*.py Z208 # Disable some pydocstyle checks: diff --git a/tests/conftest.py b/tests/conftest.py index cb4f4d9dc..7e303f744 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,14 +7,22 @@ import pytest from wemake_python_styleguide import errors -from wemake_python_styleguide.errors.base import BaseStyleViolation +from wemake_python_styleguide.errors.base import ( + ASTStyleViolation, + BaseStyleViolation, + SimpleStyleViolation, +) def _is_error_class(cls) -> bool: + base_classes = { + ASTStyleViolation, BaseStyleViolation, SimpleStyleViolation, + } + return ( inspect.isclass(cls) and issubclass(cls, BaseStyleViolation) and - cls is not BaseStyleViolation + cls not in base_classes ) @@ -26,6 +34,7 @@ def all_errors(): errors.general, errors.classes, errors.complexity, + errors.modules, ] classes = [] diff --git a/tests/test_errors.py b/tests/test_errors.py index 850540bf5..1b0110354 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -5,7 +5,7 @@ def test_all_unique_error_codes(all_errors): """Ensures that all errors have unique error codes.""" codes = [] for error in all_errors: - codes.append(error._code) + codes.append(error.code) assert len(set(codes)) == len(all_errors) @@ -13,4 +13,4 @@ def test_all_unique_error_codes(all_errors): def test_all_errors_have_description_with_code(all_errors): """Ensures that all errors have description with error code.""" for error in all_errors: - assert error._code in error.__doc__ + assert error.code in error.__doc__ diff --git a/tests/test_version.py b/tests/test_version.py index 937e5ce34..ad8366a31 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -2,6 +2,8 @@ import subprocess +from wemake_python_styleguide.version import version + def test_call_flake8_version(): """Checks that module is registered and visible in the meta data.""" @@ -9,4 +11,7 @@ def test_call_flake8_version(): ['flake8', '--version'], stderr=subprocess.STDOUT, ) - assert b'wemake-python-styleguide' in output + + output_text = output.decode('utf-8') + assert 'wemake-python-styleguide' in output_text + assert version in output_text diff --git a/tests/test_visitors/conftest.py b/tests/test_visitors/conftest.py index d24b79a3c..054b9858c 100644 --- a/tests/test_visitors/conftest.py +++ b/tests/test_visitors/conftest.py @@ -9,7 +9,7 @@ from wemake_python_styleguide.compat import maybe_set_parent from wemake_python_styleguide.options import defaults -from wemake_python_styleguide.visitors.base.visitor import BaseNodeVisitor +from wemake_python_styleguide.visitors.base import BaseNodeVisitor @pytest.fixture(scope='session') @@ -24,12 +24,12 @@ def factory(code: str) -> ast.AST: @pytest.fixture(scope='session') def assert_errors(): """Helper function to assert visitor errors.""" - def factory(visiter: BaseNodeVisitor, errors: Sequence[str]): - for index, error in enumerate(visiter.errors): - assert len(errors) > index, visiter.errors - assert error._code == errors[index]._code + def factory(visitor: BaseNodeVisitor, errors: Sequence[str]): + for index, error in enumerate(visitor.errors): + assert len(errors) > index, visitor.errors + assert error.code == errors[index].code - assert len(visiter.errors) == len(errors) + assert len(visitor.errors) == len(errors) return factory @@ -47,6 +47,7 @@ def options(): 'max_elifs': defaults.MAX_ELIFS, 'max_module_members': defaults.MAX_MODULE_MEMBERS, 'max_methods': defaults.MAX_METHODS, + 'min_module_name_length': defaults.MIN_MODULE_NAME_LENGTH, } Options = namedtuple('options', default_values.keys()) diff --git a/tests/test_visitors/test_complexity/test_counts/test_method_counts.py b/tests/test_visitors/test_ast/test_complexity/test_counts/test_method_counts.py similarity index 77% rename from tests/test_visitors/test_complexity/test_counts/test_method_counts.py rename to tests/test_visitors/test_ast/test_complexity/test_counts/test_method_counts.py index 4ae0feb91..accc8b558 100644 --- a/tests/test_visitors/test_complexity/test_counts/test_method_counts.py +++ b/tests/test_visitors/test_ast/test_complexity/test_counts/test_method_counts.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.complexity.counts import ( +from wemake_python_styleguide.visitors.ast.complexity.counts import ( MethodMembersVisitor, TooManyMethodsViolation, ) @@ -41,10 +41,10 @@ def test_method_counts_normal( """Testing that regular classes and functions work well.""" tree = parse_ast_tree(code) - visiter = MethodMembersVisitor(default_options) - visiter.visit(tree) + visitor = MethodMembersVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) @pytest.mark.parametrize('code', [ @@ -58,7 +58,7 @@ def test_method_counts_violation( tree = parse_ast_tree(code) option_values = options(max_methods=1) - visiter = MethodMembersVisitor(option_values) - visiter.visit(tree) + visitor = MethodMembersVisitor(option_values, tree=tree) + visitor.run() - assert_errors(visiter, [TooManyMethodsViolation]) + assert_errors(visitor, [TooManyMethodsViolation]) diff --git a/tests/test_visitors/test_complexity/test_counts/test_module_counts.py b/tests/test_visitors/test_ast/test_complexity/test_counts/test_module_counts.py similarity index 74% rename from tests/test_visitors/test_complexity/test_counts/test_module_counts.py rename to tests/test_visitors/test_ast/test_complexity/test_counts/test_module_counts.py index 3ead72c81..6c6849225 100644 --- a/tests/test_visitors/test_complexity/test_counts/test_module_counts.py +++ b/tests/test_visitors/test_ast/test_complexity/test_counts/test_module_counts.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.complexity.counts import ( +from wemake_python_styleguide.visitors.ast.complexity.counts import ( ModuleMembersVisitor, TooManyModuleMembersViolation, ) @@ -32,10 +32,10 @@ def test_module_counts_normal( """Testing that classes and functions in a module work well.""" tree = parse_ast_tree(code) - visiter = ModuleMembersVisitor(default_options) - visiter.visit(tree) + visitor = ModuleMembersVisitor(default_options, None) + visitor.visit(tree) - assert_errors(visiter, []) + assert_errors(visitor, []) @pytest.mark.parametrize('code', [ @@ -49,7 +49,7 @@ def test_module_counts_violation( tree = parse_ast_tree(code) option_values = options(max_module_members=1) - visiter = ModuleMembersVisitor(option_values) - visiter.visit(tree) + visitor = ModuleMembersVisitor(option_values, None) + visitor.visit(tree) - assert_errors(visiter, [TooManyModuleMembersViolation]) + assert_errors(visitor, [TooManyModuleMembersViolation]) diff --git a/tests/test_visitors/test_complexity/test_function/test_elifs.py b/tests/test_visitors/test_ast/test_complexity/test_function/test_elifs.py similarity index 75% rename from tests/test_visitors/test_complexity/test_function/test_elifs.py rename to tests/test_visitors/test_ast/test_complexity/test_function/test_elifs.py index 97eac539e..16f62f104 100644 --- a/tests/test_visitors/test_complexity/test_function/test_elifs.py +++ b/tests/test_visitors/test_ast/test_complexity/test_function/test_elifs.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.complexity.function import ( +from wemake_python_styleguide.visitors.ast.complexity.function import ( FunctionComplexityVisitor, TooManyElifsViolation, ) @@ -45,10 +45,10 @@ def test_elif_correct_count( """Testing that all `if`/`elif`/`else` stuff is allowed.""" tree = parse_ast_tree(code) - visiter = FunctionComplexityVisitor(default_options) - visiter.visit(tree) + visitor = FunctionComplexityVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) def test_elif_incorrect_count(assert_errors, parse_ast_tree, options): @@ -56,7 +56,7 @@ def test_elif_incorrect_count(assert_errors, parse_ast_tree, options): tree = parse_ast_tree(function_with_elifs) option_values = options(max_elifs=1) - visiter = FunctionComplexityVisitor(option_values) - visiter.visit(tree) + visitor = FunctionComplexityVisitor(option_values, tree=tree) + visitor.run() - assert_errors(visiter, [TooManyElifsViolation]) + assert_errors(visitor, [TooManyElifsViolation]) diff --git a/tests/test_visitors/test_complexity/test_function/test_expressions.py b/tests/test_visitors/test_ast/test_complexity/test_function/test_expressions.py similarity index 71% rename from tests/test_visitors/test_complexity/test_function/test_expressions.py rename to tests/test_visitors/test_ast/test_complexity/test_function/test_expressions.py index bea92a5d2..8f0305a00 100644 --- a/tests/test_visitors/test_complexity/test_function/test_expressions.py +++ b/tests/test_visitors/test_ast/test_complexity/test_function/test_expressions.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.complexity.function import ( +from wemake_python_styleguide.visitors.ast.complexity.function import ( FunctionComplexityVisitor, TooManyExpressionsViolation, ) @@ -28,10 +28,10 @@ def test_expressions_correct_count( """Testing that expressions counted correctly.""" tree = parse_ast_tree(code) - visiter = FunctionComplexityVisitor(default_options) - visiter.visit(tree) + visitor = FunctionComplexityVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) @pytest.mark.parametrize('code', [ @@ -42,7 +42,7 @@ def test_expressions_wrong_count(assert_errors, parse_ast_tree, options, code): tree = parse_ast_tree(code) option_values = options(max_expressions=1) - visiter = FunctionComplexityVisitor(option_values) - visiter.visit(tree) + visitor = FunctionComplexityVisitor(option_values, tree=tree) + visitor.run() - assert_errors(visiter, [TooManyExpressionsViolation]) + assert_errors(visitor, [TooManyExpressionsViolation]) diff --git a/tests/test_visitors/test_complexity/test_function/test_local_variables.py b/tests/test_visitors/test_ast/test_complexity/test_function/test_local_variables.py similarity index 82% rename from tests/test_visitors/test_complexity/test_function/test_local_variables.py rename to tests/test_visitors/test_ast/test_complexity/test_function/test_local_variables.py index 392a00bf0..1d17a8a33 100644 --- a/tests/test_visitors/test_complexity/test_function/test_local_variables.py +++ b/tests/test_visitors/test_ast/test_complexity/test_function/test_local_variables.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.complexity.function import ( +from wemake_python_styleguide.visitors.ast.complexity.function import ( FunctionComplexityVisitor, TooManyLocalsViolation, ) @@ -46,10 +46,10 @@ def test_locals_correct_count(assert_errors, parse_ast_tree, options, code): option_values = options(max_local_variables=2) tree = parse_ast_tree(code) - visiter = FunctionComplexityVisitor(option_values) - visiter.visit(tree) + visitor = FunctionComplexityVisitor(option_values, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) @pytest.mark.parametrize('code', [ @@ -67,7 +67,7 @@ def test_locals_wrong_count(assert_errors, parse_ast_tree, options, code): option_values = options(max_local_variables=1) tree = parse_ast_tree(code) - visiter = FunctionComplexityVisitor(option_values) - visiter.visit(tree) + visitor = FunctionComplexityVisitor(option_values, tree=tree) + visitor.run() - assert_errors(visiter, [TooManyLocalsViolation]) + assert_errors(visitor, [TooManyLocalsViolation]) diff --git a/tests/test_visitors/test_complexity/test_function/test_returns.py b/tests/test_visitors/test_ast/test_complexity/test_function/test_returns.py similarity index 66% rename from tests/test_visitors/test_complexity/test_function/test_returns.py rename to tests/test_visitors/test_ast/test_complexity/test_function/test_returns.py index 55b2bc624..bfd02a443 100644 --- a/tests/test_visitors/test_complexity/test_function/test_returns.py +++ b/tests/test_visitors/test_ast/test_complexity/test_function/test_returns.py @@ -2,14 +2,12 @@ import pytest -from wemake_python_styleguide.visitors.complexity.function import ( +from wemake_python_styleguide.visitors.ast.complexity.function import ( FunctionComplexityVisitor, TooManyReturnsViolation, ) -function_without_returns = """ -def function(): ... -""" +function_without_returns = 'def function(): ...' function_with_returns = """ def function(): @@ -29,10 +27,10 @@ def test_returns_correct_count( """Testing that returns counted correctly.""" tree = parse_ast_tree(code) - visiter = FunctionComplexityVisitor(default_options) - visiter.visit(tree) + visitor = FunctionComplexityVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) @pytest.mark.parametrize('code', [ @@ -43,7 +41,7 @@ def test_returns_wrong_count(assert_errors, parse_ast_tree, options, code): tree = parse_ast_tree(code) option_values = options(max_returns=1) - visiter = FunctionComplexityVisitor(option_values) - visiter.visit(tree) + visitor = FunctionComplexityVisitor(option_values, tree=tree) + visitor.run() - assert_errors(visiter, [TooManyReturnsViolation]) + assert_errors(visitor, [TooManyReturnsViolation]) diff --git a/tests/test_visitors/test_complexity/test_nested/test_nested_classes.py b/tests/test_visitors/test_ast/test_complexity/test_nested/test_nested_classes.py similarity index 74% rename from tests/test_visitors/test_complexity/test_nested/test_nested_classes.py rename to tests/test_visitors/test_ast/test_complexity/test_nested/test_nested_classes.py index a056857d8..97bd20cfc 100644 --- a/tests/test_visitors/test_complexity/test_nested/test_nested_classes.py +++ b/tests/test_visitors/test_ast/test_complexity/test_nested/test_nested_classes.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.complexity.nested import ( +from wemake_python_styleguide.visitors.ast.complexity.nested import ( NESTED_CLASSES_WHITELIST, NestedClassViolation, NestedComplexityVisitor, @@ -34,10 +34,10 @@ def test_nested_class(assert_errors, parse_ast_tree, code, default_options): """Testing that nested classes are restricted.""" tree = parse_ast_tree(code.format('NestedClass')) - visiter = NestedComplexityVisitor(default_options) - visiter.visit(tree) + visitor = NestedComplexityVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [NestedClassViolation]) + assert_errors(visitor, [NestedClassViolation]) @pytest.mark.parametrize('whitelist_name', NESTED_CLASSES_WHITELIST) @@ -50,10 +50,10 @@ def test_whitelist_nested_classes( """Testing that it is possible to nest whitelisted classes.""" tree = parse_ast_tree(code.format(whitelist_name)) - visiter = NestedComplexityVisitor(default_options) - visiter.visit(tree) + visitor = NestedComplexityVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) @pytest.mark.parametrize('whitelist_name', [ @@ -70,10 +70,10 @@ def test_whitelist_nested_classes_in_functions( """Testing that it is restricted to nest any classes in functions.""" tree = parse_ast_tree(code.format(whitelist_name)) - visiter = NestedComplexityVisitor(default_options) - visiter.visit(tree) + visitor = NestedComplexityVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [NestedClassViolation]) + assert_errors(visitor, [NestedClassViolation]) def test_ordinary_class(assert_errors, parse_ast_tree, default_options): @@ -83,7 +83,7 @@ class Ordinary: def method(self): ... """) - visiter = NestedComplexityVisitor(default_options) - visiter.visit(tree) + visitor = NestedComplexityVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_complexity/test_nested/test_nested_functions.py b/tests/test_visitors/test_ast/test_complexity/test_nested/test_nested_functions.py similarity index 72% rename from tests/test_visitors/test_complexity/test_nested/test_nested_functions.py rename to tests/test_visitors/test_ast/test_complexity/test_nested/test_nested_functions.py index 4a5f34ca9..bc14734ff 100644 --- a/tests/test_visitors/test_complexity/test_nested/test_nested_functions.py +++ b/tests/test_visitors/test_ast/test_complexity/test_nested/test_nested_functions.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.complexity.nested import ( +from wemake_python_styleguide.visitors.ast.complexity.nested import ( NESTED_FUNCTIONS_WHITELIST, NestedComplexityVisitor, NestedFunctionViolation, @@ -28,10 +28,10 @@ def test_nested_function(assert_errors, parse_ast_tree, code, default_options): """Testing that nested functions are restricted.""" tree = parse_ast_tree(code.format('nested')) - visiter = NestedComplexityVisitor(default_options) - visiter.visit(tree) + visitor = NestedComplexityVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [NestedFunctionViolation]) + assert_errors(visitor, [NestedFunctionViolation]) @pytest.mark.parametrize('whitelist_name', NESTED_FUNCTIONS_WHITELIST) @@ -45,10 +45,10 @@ def test_whitelist_nested_functions( """Testing that it is possible to nest whitelisted functions.""" tree = parse_ast_tree(code.format(whitelist_name)) - visiter = NestedComplexityVisitor(default_options) - visiter.visit(tree) + visitor = NestedComplexityVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) def test_lambda_nested_functions( @@ -60,10 +60,10 @@ def container(): lazy_value = lambda: 12 """) - visiter = NestedComplexityVisitor(default_options) - visiter.visit(tree) + visitor = NestedComplexityVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) def test_lambda_nested_lambdas(assert_errors, parse_ast_tree, default_options): @@ -77,10 +77,10 @@ def container(): nested_lambda = lambda: lambda value: value + 12 """) - visiter = NestedComplexityVisitor(default_options) - visiter.visit(tree) + visitor = NestedComplexityVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [NestedFunctionViolation]) + assert_errors(visitor, [NestedFunctionViolation]) def test_lambda_nested_method(assert_errors, parse_ast_tree, default_options): @@ -91,7 +91,7 @@ def container(self): lazy_value = lambda: 12 """) - visiter = NestedComplexityVisitor(default_options) - visiter.visit(tree) + visitor = NestedComplexityVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_complexity/test_offset_visitor.py b/tests/test_visitors/test_ast/test_complexity/test_offset_visitor.py similarity index 84% rename from tests/test_visitors/test_complexity/test_offset_visitor.py rename to tests/test_visitors/test_ast/test_complexity/test_offset_visitor.py index b07d3d597..64a4e34c4 100644 --- a/tests/test_visitors/test_complexity/test_offset_visitor.py +++ b/tests/test_visitors/test_ast/test_complexity/test_offset_visitor.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.complexity.offset import ( +from wemake_python_styleguide.visitors.ast.complexity.offset import ( OffsetVisitor, TooDeepNestingViolation, ) @@ -69,10 +69,10 @@ def test_nested_offset(assert_errors, parse_ast_tree, code, default_options): """Testing that nested expression with default options works well.""" tree = parse_ast_tree(code) - visiter = OffsetVisitor(default_options) - visiter.visit(tree) + visitor = OffsetVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) @pytest.mark.parametrize('code, number_of_errors', [ @@ -91,8 +91,8 @@ def test_nested_offset_errors( tree = parse_ast_tree(code) option_values = options(max_offset_blocks=1) - visiter = OffsetVisitor(option_values) - visiter.visit(tree) + visitor = OffsetVisitor(option_values, tree=tree) + visitor.run() errors = [TooDeepNestingViolation for _ in range(number_of_errors)] - assert_errors(visiter, errors) + assert_errors(visitor, errors) diff --git a/tests/test_visitors/test_wrong_class/test_base_class.py b/tests/test_visitors/test_ast/test_wrong_class/test_base_class.py similarity index 60% rename from tests/test_visitors/test_wrong_class/test_base_class.py rename to tests/test_visitors/test_ast/test_wrong_class/test_base_class.py index 0e30ad4ef..10fe7dcc9 100644 --- a/tests/test_visitors/test_wrong_class/test_base_class.py +++ b/tests/test_visitors/test_ast/test_wrong_class/test_base_class.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.wrong_class import ( +from wemake_python_styleguide.visitors.ast.wrong_class import ( RequiredBaseClassViolation, WrongClassVisitor, ) @@ -10,14 +10,12 @@ def test_wrong_base_class(assert_errors, parse_ast_tree, default_options): """Testing that not using explicit base class is forbiden.""" - tree = parse_ast_tree(""" - class WithoutBase: ... - """) + tree = parse_ast_tree('class WithoutBase: ...') - visiter = WrongClassVisitor(default_options) - visiter.visit(tree) + visitor = WrongClassVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [RequiredBaseClassViolation]) + assert_errors(visitor, [RequiredBaseClassViolation]) def test_wrong_base_class_nested( @@ -29,10 +27,10 @@ class Model(object): class Meta: ... """) - visiter = WrongClassVisitor(default_options) - visiter.visit(tree) + visitor = WrongClassVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [RequiredBaseClassViolation]) + assert_errors(visitor, [RequiredBaseClassViolation]) def test_correct_base_class_nested( @@ -44,10 +42,10 @@ class Model(object): class Meta(object): ... """) - visiter = WrongClassVisitor(default_options) - visiter.visit(tree) + visitor = WrongClassVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) @pytest.mark.parametrize('base', [ @@ -61,11 +59,9 @@ def test_regular_base_classes( assert_errors, parse_ast_tree, base, default_options, ): """Testing that regular base classes work.""" - tree = parse_ast_tree(""" - class Example({0}): ... - """.format(base)) + tree = parse_ast_tree('class Example({0}): ...'.format(base)) - visiter = WrongClassVisitor(default_options) - visiter.visit(tree) + visitor = WrongClassVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_wrong_class/test_magic_methods.py b/tests/test_visitors/test_ast/test_wrong_class/test_magic_methods.py similarity index 72% rename from tests/test_visitors/test_wrong_class/test_magic_methods.py rename to tests/test_visitors/test_ast/test_wrong_class/test_magic_methods.py index 2bda2a5d1..c029f29fe 100644 --- a/tests/test_visitors/test_wrong_class/test_magic_methods.py +++ b/tests/test_visitors/test_ast/test_wrong_class/test_magic_methods.py @@ -3,7 +3,7 @@ import pytest from wemake_python_styleguide.constants import BAD_MAGIC_METHODS -from wemake_python_styleguide.visitors.wrong_class import ( +from wemake_python_styleguide.visitors.ast.wrong_class import ( BadMagicMethodViolation, WrongClassVisitor, ) @@ -21,10 +21,10 @@ def test_wrong_magic_used( """Testing that some magic methods are restricted.""" tree = parse_ast_tree(magic_method.format(method)) - visiter = WrongClassVisitor(default_options) - visiter.visit(tree) + visitor = WrongClassVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [BadMagicMethodViolation]) + assert_errors(visitor, [BadMagicMethodViolation]) @pytest.mark.parametrize('method', [ @@ -39,7 +39,7 @@ def test_regular_method_used( """Testing that other methods are working fine.""" tree = parse_ast_tree(magic_method.format(method)) - visiter = WrongClassVisitor(default_options) - visiter.visit(tree) + visitor = WrongClassVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_wrong_class/test_staticmethod.py b/tests/test_visitors/test_ast/test_wrong_class/test_staticmethod.py similarity index 70% rename from tests/test_visitors/test_wrong_class/test_staticmethod.py rename to tests/test_visitors/test_ast/test_wrong_class/test_staticmethod.py index 6a7474807..afbdf74ef 100644 --- a/tests/test_visitors/test_wrong_class/test_staticmethod.py +++ b/tests/test_visitors/test_ast/test_wrong_class/test_staticmethod.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.wrong_class import ( +from wemake_python_styleguide.visitors.ast.wrong_class import ( StaticMethodViolation, WrongClassVisitor, ) @@ -18,10 +18,10 @@ def test_staticmethod_used(assert_errors, parse_ast_tree, default_options): """Testing that some built-in functions are restricted as decorators.""" tree = parse_ast_tree(decorated_method.format('staticmethod')) - visiter = WrongClassVisitor(default_options) - visiter.visit(tree) + visitor = WrongClassVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [StaticMethodViolation]) + assert_errors(visitor, [StaticMethodViolation]) @pytest.mark.parametrize('decorator', [ @@ -35,7 +35,7 @@ def test_regular_decorator_used( """Testing that other decorators are allowed.""" tree = parse_ast_tree(decorated_method.format(decorator)) - visiter = WrongClassVisitor(default_options) - visiter.visit(tree) + visitor = WrongClassVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_ast/test_wrong_contents/test_empty_init.py b/tests/test_visitors/test_ast/test_wrong_contents/test_empty_init.py new file mode 100644 index 000000000..623d31a98 --- /dev/null +++ b/tests/test_visitors/test_ast/test_wrong_contents/test_empty_init.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + +import pytest + +from wemake_python_styleguide.visitors.ast.wrong_contents import ( + InitModuleHasLogicViolation, + WrongContentsVisitor, +) + +empty_module = '' +module_with_docstring = """ +'''Hi, am a docstring inside a module.''' +""" + +module_with_comments = """ +# Some comment about what is going on. +# Commented code: +# print('hi') +""" + +module_with_one_import = 'from some import other' +module_with_imports = """ +from one import one_func +from two import two_func +""" + +module_with_logic = """ +try: + import some + has_some = True +except ImportError: + some = None + has_some = False +""" + + +@pytest.mark.parametrize('code', [ + empty_module, + module_with_docstring, + module_with_comments, +]) +def test_init_without_logic( + assert_errors, parse_ast_tree, code, default_options, +): + """Testing that `__init__` without logic is allowed.""" + tree = parse_ast_tree(code) + + visitor = WrongContentsVisitor( + default_options, tree=tree, filename='__init__.py', + ) + visitor.run() + + assert_errors(visitor, []) + + +@pytest.mark.parametrize('code', [ + module_with_imports, + module_with_one_import, + module_with_logic, +]) +def test_init_with_logic( + assert_errors, parse_ast_tree, code, default_options, +): + """Testing that `__init__` with logic is restricted.""" + tree = parse_ast_tree(code) + + visitor = WrongContentsVisitor( + default_options, tree=tree, filename='__init__.py', + ) + visitor.run() + + assert_errors(visitor, [InitModuleHasLogicViolation]) diff --git a/tests/test_visitors/test_ast/test_wrong_contents/test_empty_modules.py b/tests/test_visitors/test_ast/test_wrong_contents/test_empty_modules.py new file mode 100644 index 000000000..490b2e578 --- /dev/null +++ b/tests/test_visitors/test_ast/test_wrong_contents/test_empty_modules.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +import pytest + +from wemake_python_styleguide.visitors.ast.wrong_contents import ( + EmptyModuleViolation, + WrongContentsVisitor, +) + + +@pytest.mark.parametrize('filename', [ + 'empty.py', + '/home/user/logics.py', + 'partial/views.py', + 'C:/path/package/module.py', +]) +def test_simple_filename( + assert_errors, parse_ast_tree, filename, default_options, +): + """Testing that simple file names should not be empty.""" + tree = parse_ast_tree('') + + visitor = WrongContentsVisitor( + default_options, tree=tree, filename=filename, + ) + visitor.run() + + assert_errors(visitor, [EmptyModuleViolation]) diff --git a/tests/test_visitors/test_wrong_function_call/test_wrong_function_calls.py b/tests/test_visitors/test_ast/test_wrong_function_call/test_wrong_function_calls.py similarity index 64% rename from tests/test_visitors/test_wrong_function_call/test_wrong_function_calls.py rename to tests/test_visitors/test_ast/test_wrong_function_call/test_wrong_function_calls.py index e1a194862..a531030e5 100644 --- a/tests/test_visitors/test_wrong_function_call/test_wrong_function_calls.py +++ b/tests/test_visitors/test_ast/test_wrong_function_call/test_wrong_function_calls.py @@ -2,19 +2,14 @@ import pytest -from wemake_python_styleguide.visitors.wrong_function_call import ( +from wemake_python_styleguide.visitors.ast.wrong_function_call import ( BAD_FUNCTIONS, WrongFunctionCallViolation, WrongFunctionCallVisitor, ) -regular_call = """ -{0}(*args, **kwargs) -""" - -assignment_call = """ -test_result = {0}(*args, **kwargs) -""" +regular_call = '{0}(*args, **kwargs)' +assignment_call = 'test_result = {0}(*args, **kwargs)' nested_function_call = """ def proxy(*args, **kwargs): @@ -34,22 +29,22 @@ def test_wrong_function_called( """Testing that some built-in functions are restricted.""" tree = parse_ast_tree(code.format(bad_function)) - visiter = WrongFunctionCallVisitor(default_options) - visiter.visit(tree) + visitor = WrongFunctionCallVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [WrongFunctionCallViolation]) + assert_errors(visitor, [WrongFunctionCallViolation]) def test_wrong_decorator_used(assert_errors, parse_ast_tree, default_options): """Testing that some built-in functions are restricted as decorators.""" - tree = parse_ast_tree(""" - some_static = staticmethod(some_function) - """) + tree = parse_ast_tree( + 'some_static = staticmethod(some_function)', + ) - visiter = WrongFunctionCallVisitor(default_options) - visiter.visit(tree) + visitor = WrongFunctionCallVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [WrongFunctionCallViolation]) + assert_errors(visitor, [WrongFunctionCallViolation]) @pytest.mark.parametrize('good_function', ['len', 'abs', 'max', 'custom']) @@ -64,7 +59,7 @@ def test_regular_function_called( """Testing that other functions are not restricted.""" tree = parse_ast_tree(code.format(good_function)) - visiter = WrongFunctionCallVisitor(default_options) - visiter.visit(tree) + visitor = WrongFunctionCallVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_wrong_import/test_dotted_raw_import.py b/tests/test_visitors/test_ast/test_wrong_import/test_dotted_raw_import.py similarity index 67% rename from tests/test_visitors/test_wrong_import/test_dotted_raw_import.py rename to tests/test_visitors/test_ast/test_wrong_import/test_dotted_raw_import.py index d1781972b..f706ee41b 100644 --- a/tests/test_visitors/test_wrong_import/test_dotted_raw_import.py +++ b/tests/test_visitors/test_ast/test_wrong_import/test_dotted_raw_import.py @@ -2,26 +2,15 @@ import pytest -from wemake_python_styleguide.visitors.wrong_import import ( +from wemake_python_styleguide.visitors.ast.wrong_import import ( DottedRawImportViolation, WrongImportVisitor, ) -regular_import = """ -import {0} -""" - -regular_import_with_alias = """ -import {0} as alias -""" - -from_import = """ -from {0} import some -""" - -from_import_with_alias = """ -from {0} import some as alias -""" +regular_import = 'import {0}' +regular_import_with_alias = 'import {0} as alias' +from_import = 'from {0} import some' +from_import_with_alias = 'from {0} import some as alias' @pytest.mark.parametrize('code', [ @@ -38,10 +27,10 @@ def test_wrong_dotted_import( """Testing that dotted raw imports are restricted.""" tree = parse_ast_tree(code.format(to_import)) - visiter = WrongImportVisitor(default_options) - visiter.visit(tree) + visitor = WrongImportVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [DottedRawImportViolation]) + assert_errors(visitor, [DottedRawImportViolation]) @pytest.mark.parametrize('code', [ @@ -58,10 +47,10 @@ def test_correct_flat_import( """Testing that flat raw imports are allowed.""" tree = parse_ast_tree(code.format(to_import)) - visiter = WrongImportVisitor(default_options) - visiter.visit(tree) + visitor = WrongImportVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) @pytest.mark.parametrize('code', [ @@ -79,7 +68,7 @@ def test_regular_from_import( """Testing that dotted `from` imports are allowed.""" tree = parse_ast_tree(code.format(to_import)) - visiter = WrongImportVisitor(default_options) - visiter.visit(tree) + visitor = WrongImportVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_wrong_import/test_future_imports.py b/tests/test_visitors/test_ast/test_wrong_import/test_future_imports.py similarity index 62% rename from tests/test_visitors/test_wrong_import/test_future_imports.py rename to tests/test_visitors/test_ast/test_wrong_import/test_future_imports.py index dc0d6be35..c3f735820 100644 --- a/tests/test_visitors/test_wrong_import/test_future_imports.py +++ b/tests/test_visitors/test_ast/test_wrong_import/test_future_imports.py @@ -3,18 +3,14 @@ import pytest from wemake_python_styleguide.constants import FUTURE_IMPORTS_WHITELIST -from wemake_python_styleguide.visitors.wrong_import import ( +from wemake_python_styleguide.visitors.ast.wrong_import import ( FutureImportViolation, WrongImportVisitor, ) -future_import = """ -from __future__ import {0} -""" +future_import = 'from __future__ import {0}' -future_import_alias = """ -from __future__ import {0} as some_alias -""" +future_import_alias = 'from __future__ import {0} as some_alias' @pytest.mark.parametrize('code', [ @@ -31,24 +27,24 @@ def test_wrong_future_import( """Testing that future imports are restricted.""" tree = parse_ast_tree(code.format(to_import)) - visiter = WrongImportVisitor(default_options) - visiter.visit(tree) + visitor = WrongImportVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [FutureImportViolation]) + assert_errors(visitor, [FutureImportViolation]) def test_wrong_multiple_future_import( assert_errors, parse_ast_tree, default_options, ): """Testing that multiple future imports are restricted.""" - tree = parse_ast_tree(""" - from __future__ import print_function, unicode_literals - """) + tree = parse_ast_tree( + 'from __future__ import print_function, unicode_literals', + ) - visiter = WrongImportVisitor(default_options) - visiter.visit(tree) + visitor = WrongImportVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [FutureImportViolation, FutureImportViolation]) + assert_errors(visitor, [FutureImportViolation, FutureImportViolation]) @pytest.mark.parametrize('code', [ @@ -62,7 +58,7 @@ def test_correct_future_import( """Testing that some future imports are not restricted.""" tree = parse_ast_tree(code.format(to_import)) - visiter = WrongImportVisitor(default_options) - visiter.visit(tree) + visitor = WrongImportVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_wrong_import/test_nested_imports.py b/tests/test_visitors/test_ast/test_wrong_import/test_nested_imports.py similarity index 73% rename from tests/test_visitors/test_wrong_import/test_nested_imports.py rename to tests/test_visitors/test_ast/test_wrong_import/test_nested_imports.py index 987cd6c96..7bf4a5ef6 100644 --- a/tests/test_visitors/test_wrong_import/test_nested_imports.py +++ b/tests/test_visitors/test_ast/test_wrong_import/test_nested_imports.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.wrong_import import ( +from wemake_python_styleguide.visitors.ast.wrong_import import ( NestedImportViolation, WrongImportVisitor, ) @@ -45,13 +45,9 @@ def with_import(self): # Correct imports: -regular_import = """ -import os -""" - -regular_from_import = """ -from os import path -""" +regular_import = 'import os' +regular_from_import = 'from os import path' +regular_nested_import = 'from core.errors import Error' @pytest.mark.parametrize('code', [ @@ -66,21 +62,22 @@ def test_nested_import(assert_errors, parse_ast_tree, code, default_options): """Testing that nested imports are restricted.""" tree = parse_ast_tree(code) - visiter = WrongImportVisitor(default_options) - visiter.visit(tree) + visitor = WrongImportVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [NestedImportViolation]) + assert_errors(visitor, [NestedImportViolation]) @pytest.mark.parametrize('code', [ regular_import, regular_from_import, + regular_nested_import, ]) def test_regular_imports(assert_errors, parse_ast_tree, code, default_options): """Testing that regular imports are allowed.""" tree = parse_ast_tree(code) - visiter = WrongImportVisitor(default_options) - visiter.visit(tree) + visitor = WrongImportVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_ast/test_wrong_import/test_relative_imports.py b/tests/test_visitors/test_ast/test_wrong_import/test_relative_imports.py new file mode 100644 index 000000000..5102fe5db --- /dev/null +++ b/tests/test_visitors/test_ast/test_wrong_import/test_relative_imports.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +import pytest + +from wemake_python_styleguide.visitors.ast.wrong_import import ( + LocalFolderImportViolation, + WrongImportVisitor, +) + +same_level_relative_import = 'from . import some' +same_level_relative_import_sibling = 'from .some import MyClass' +parent_level_relative_import = 'from .. import some' +parent_level_relative_import_sibling = 'from ..some import MyClass' + +# Correct: +regular_import = 'import os' +regular_from_import = 'from os import path' +regular_nested_import = 'from some.package import Thing' + + +@pytest.mark.parametrize('code', [ + same_level_relative_import, + same_level_relative_import_sibling, + parent_level_relative_import, + parent_level_relative_import_sibling, +]) +def test_local_folder_import( + assert_errors, parse_ast_tree, code, default_options, +): + """Testing that relative to local folder imports are restricted.""" + tree = parse_ast_tree(code) + + visitor = WrongImportVisitor(default_options, tree=tree) + visitor.run() + + assert_errors(visitor, [LocalFolderImportViolation]) + + +@pytest.mark.parametrize('code', [ + regular_import, + regular_from_import, + regular_nested_import, +]) +def test_regular_import(assert_errors, parse_ast_tree, code, default_options): + """Testing that regular imports are allowed.""" + tree = parse_ast_tree(code) + + visitor = WrongImportVisitor(default_options, tree=tree) + visitor.run() + + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_wrong_import/test_same_alias_import.py b/tests/test_visitors/test_ast/test_wrong_import/test_same_alias_import.py similarity index 66% rename from tests/test_visitors/test_wrong_import/test_same_alias_import.py rename to tests/test_visitors/test_ast/test_wrong_import/test_same_alias_import.py index 1238508c4..63ce28579 100644 --- a/tests/test_visitors/test_wrong_import/test_same_alias_import.py +++ b/tests/test_visitors/test_ast/test_wrong_import/test_same_alias_import.py @@ -2,18 +2,13 @@ import pytest -from wemake_python_styleguide.visitors.wrong_import import ( +from wemake_python_styleguide.visitors.ast.wrong_import import ( SameAliasImportViolation, WrongImportVisitor, ) -regular_import = """ -import os as {0} -""" - -from_import = """ -from sys import os as {0} -""" +regular_import = 'import os as {0}' +from_import = 'from sys import os as {0}' @pytest.mark.parametrize('code', [ @@ -26,10 +21,10 @@ def test_same_alias_import( """Testing that imports with the same aliases are restricted.""" tree = parse_ast_tree(code.format('os')) - visiter = WrongImportVisitor(default_options) - visiter.visit(tree) + visitor = WrongImportVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [SameAliasImportViolation]) + assert_errors(visitor, [SameAliasImportViolation]) @pytest.mark.parametrize('code', [ @@ -47,7 +42,7 @@ def test_other_alias_name( """Testing that imports with other aliases are allowed.""" tree = parse_ast_tree(code.format(to_import)) - visiter = WrongImportVisitor(default_options) - visiter.visit(tree) + visitor = WrongImportVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_wrong_keyword/test_bare_raise.py b/tests/test_visitors/test_ast/test_wrong_keyword/test_bare_raise.py similarity index 70% rename from tests/test_visitors/test_wrong_keyword/test_bare_raise.py rename to tests/test_visitors/test_ast/test_wrong_keyword/test_bare_raise.py index 294a2d5a6..8705d2197 100644 --- a/tests/test_visitors/test_wrong_keyword/test_bare_raise.py +++ b/tests/test_visitors/test_ast/test_wrong_keyword/test_bare_raise.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.wrong_keyword import ( +from wemake_python_styleguide.visitors.ast.wrong_keyword import ( BareRiseViolation, WrongRaiseVisitor, ) @@ -27,10 +27,10 @@ def test_bare_raise_keyword( """Testing that bare raise keyword is restricted outside of except.""" tree = parse_ast_tree(code) - visiter = WrongRaiseVisitor(default_options) - visiter.visit(tree) + visitor = WrongRaiseVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [BareRiseViolation]) + assert_errors(visitor, [BareRiseViolation]) def test_normal_raise_keyword( @@ -41,10 +41,10 @@ def test_normal_raise_keyword( raise ValueError('Some error') """) - visiter = WrongRaiseVisitor(default_options) - visiter.visit(tree) + visitor = WrongRaiseVisitor(default_options, tree=tree) + visitor.run() - assert visiter.errors == [] + assert_errors(visitor, []) def test_bare_raise_in_except_keyword( @@ -58,7 +58,7 @@ def test_bare_raise_in_except_keyword( raise """) - visiter = WrongRaiseVisitor(default_options) - visiter.visit(tree) + visitor = WrongRaiseVisitor(default_options, tree=tree) + visitor.run() - assert visiter.errors == [] + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_wrong_keyword/test_del.py b/tests/test_visitors/test_ast/test_wrong_keyword/test_del.py similarity index 69% rename from tests/test_visitors/test_wrong_keyword/test_del.py rename to tests/test_visitors/test_ast/test_wrong_keyword/test_del.py index 1a7e39425..02ece2448 100644 --- a/tests/test_visitors/test_wrong_keyword/test_del.py +++ b/tests/test_visitors/test_ast/test_wrong_keyword/test_del.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from wemake_python_styleguide.visitors.wrong_keyword import ( +from wemake_python_styleguide.visitors.ast.wrong_keyword import ( WrongKeywordViolation, WrongKeywordVisitor, ) @@ -15,10 +15,10 @@ def check_del(): del s """) - visiter = WrongKeywordVisitor(default_options) - visiter.visit(tree) + visitor = WrongKeywordVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [ + assert_errors(visitor, [ WrongKeywordViolation, WrongKeywordViolation, ]) diff --git a/tests/test_visitors/test_wrong_keyword/test_global.py b/tests/test_visitors/test_ast/test_wrong_keyword/test_global.py similarity index 73% rename from tests/test_visitors/test_wrong_keyword/test_global.py rename to tests/test_visitors/test_ast/test_wrong_keyword/test_global.py index e498b49c2..a3f826e9c 100644 --- a/tests/test_visitors/test_wrong_keyword/test_global.py +++ b/tests/test_visitors/test_ast/test_wrong_keyword/test_global.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.wrong_keyword import ( +from wemake_python_styleguide.visitors.ast.wrong_keyword import ( WrongKeywordViolation, WrongKeywordVisitor, ) @@ -31,7 +31,7 @@ def test_global_keywords(assert_errors, parse_ast_tree, code, default_options): """Testing that `global` and `nonlocal` keywords are restricted.""" tree = parse_ast_tree(code) - visiter = WrongKeywordVisitor(default_options) - visiter.visit(tree) + visitor = WrongKeywordVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [WrongKeywordViolation]) + assert_errors(visitor, [WrongKeywordViolation]) diff --git a/tests/test_visitors/test_wrong_keyword/test_pass.py b/tests/test_visitors/test_ast/test_wrong_keyword/test_pass.py similarity index 63% rename from tests/test_visitors/test_wrong_keyword/test_pass.py rename to tests/test_visitors/test_ast/test_wrong_keyword/test_pass.py index ce4973815..f05c41138 100644 --- a/tests/test_visitors/test_wrong_keyword/test_pass.py +++ b/tests/test_visitors/test_ast/test_wrong_keyword/test_pass.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.wrong_keyword import ( +from wemake_python_styleguide.visitors.ast.wrong_keyword import ( WrongKeywordViolation, WrongKeywordVisitor, ) @@ -23,17 +23,26 @@ def method(self): pass """ +pass_condition = """ +for i in 'abc': + if i == 'a': + pass + else: + print(i) +""" + @pytest.mark.parametrize('code', [ pass_function, pass_class, pass_method, + pass_condition, ]) def test_pass_keyword(assert_errors, parse_ast_tree, code, default_options): """Testing that pass keyword is restricted inside different definitions.""" tree = parse_ast_tree(code) - visiter = WrongKeywordVisitor(default_options) - visiter.visit(tree) + visitor = WrongKeywordVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [WrongKeywordViolation]) + assert_errors(visitor, [WrongKeywordViolation]) diff --git a/tests/test_visitors/test_wrong_keyword/test_raise_notimplemented.py b/tests/test_visitors/test_ast/test_wrong_keyword/test_raise_notimplemented.py similarity index 65% rename from tests/test_visitors/test_wrong_keyword/test_raise_notimplemented.py rename to tests/test_visitors/test_ast/test_wrong_keyword/test_raise_notimplemented.py index 50d5aeb68..91eafa50d 100644 --- a/tests/test_visitors/test_wrong_keyword/test_raise_notimplemented.py +++ b/tests/test_visitors/test_ast/test_wrong_keyword/test_raise_notimplemented.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.wrong_keyword import ( +from wemake_python_styleguide.visitors.ast.wrong_keyword import ( RaiseNotImplementedViolation, WrongRaiseVisitor, ) @@ -16,7 +16,7 @@ def check_not_implemented(self): raise_not_implemented_type = """ class CheckAbstractMethods(): def check_not_implemented_without_call(self): - raise NotImplemented # error here + raise NotImplemented """ @@ -30,21 +30,19 @@ def test_raise_not_implemented( """Testing that `raise NotImplemented` is restricted.""" tree = parse_ast_tree(code) - visiter = WrongRaiseVisitor(default_options) - visiter.visit(tree) + visitor = WrongRaiseVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [RaiseNotImplementedViolation]) + assert_errors(visitor, [RaiseNotImplementedViolation]) def test_raise_not_implemented_error( assert_errors, parse_ast_tree, default_options, ): """Testing that `raise NotImplementedError` is allowed.""" - tree = parse_ast_tree(""" - raise NotImplementedError() - """) + tree = parse_ast_tree('raise NotImplementedError()') - visiter = WrongRaiseVisitor(default_options) - visiter.visit(tree) + visitor = WrongRaiseVisitor(default_options, tree=tree) + visitor.run() - assert visiter.errors == [] + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_wrong_name/test_class_attributes.py b/tests/test_visitors/test_ast/test_wrong_name/test_class_attributes.py similarity index 76% rename from tests/test_visitors/test_wrong_name/test_class_attributes.py rename to tests/test_visitors/test_ast/test_wrong_name/test_class_attributes.py index 82603d611..619d430ce 100644 --- a/tests/test_visitors/test_wrong_name/test_class_attributes.py +++ b/tests/test_visitors/test_ast/test_wrong_name/test_class_attributes.py @@ -4,7 +4,7 @@ import pytest -from wemake_python_styleguide.visitors.wrong_name import ( +from wemake_python_styleguide.visitors.ast.wrong_name import ( BAD_VARIABLE_NAMES, PrivateNameViolation, TooShortVariableNameViolation, @@ -35,10 +35,10 @@ def test_wrong_attributes_names( """Testing that attribute can not have blacklisted names.""" tree = parse_ast_tree(code.format(bad_name)) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [WrongVariableNameViolation]) + assert_errors(visitor, [WrongVariableNameViolation]) @pytest.mark.parametrize('short_name', string.ascii_letters) @@ -52,10 +52,10 @@ def test_too_short_attribute_names( """Testing that attribute can not have too short names.""" tree = parse_ast_tree(code.format(short_name)) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [TooShortVariableNameViolation]) + assert_errors(visitor, [TooShortVariableNameViolation]) @pytest.mark.parametrize('code', [ @@ -68,10 +68,10 @@ def test_private_attribute_names( """Testing that attribute can not have private names.""" tree = parse_ast_tree(code.format('__private_name')) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [PrivateNameViolation]) + assert_errors(visitor, [PrivateNameViolation]) @pytest.mark.parametrize('correct_name', [ @@ -91,7 +91,7 @@ def test_correct_attribute_name( """Testing that attribute can have normal names.""" tree = parse_ast_tree(code.format(correct_name)) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_wrong_name/test_function_args_names.py b/tests/test_visitors/test_ast/test_wrong_name/test_function_args_names.py similarity index 84% rename from tests/test_visitors/test_wrong_name/test_function_args_names.py rename to tests/test_visitors/test_ast/test_wrong_name/test_function_args_names.py index 4734cc4bf..7f5ef5253 100644 --- a/tests/test_visitors/test_wrong_name/test_function_args_names.py +++ b/tests/test_visitors/test_ast/test_wrong_name/test_function_args_names.py @@ -4,7 +4,7 @@ import pytest -from wemake_python_styleguide.visitors.wrong_name import ( +from wemake_python_styleguide.visitors.ast.wrong_name import ( BAD_VARIABLE_NAMES, PrivateNameViolation, TooShortVariableNameViolation, @@ -55,10 +55,10 @@ def test_wrong_function_arguments( """Testing that function can not have blacklisted arguments.""" tree = parse_ast_tree(code.format('x', bad_name)) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [ + assert_errors(visitor, [ TooShortVariableNameViolation, WrongVariableNameViolation, ]) @@ -79,10 +79,10 @@ def test_too_short_function_arguments( """Testing that function can not have too short arguments.""" tree = parse_ast_tree(code.format(short_name, 'data')) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [ + assert_errors(visitor, [ TooShortVariableNameViolation, WrongVariableNameViolation, ]) @@ -102,10 +102,10 @@ def test_private_function_arguments( """Testing that function can not have private arguments.""" tree = parse_ast_tree(code.format('__private', '__name')) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [ + assert_errors(visitor, [ PrivateNameViolation, PrivateNameViolation, ]) @@ -125,7 +125,7 @@ def test_correct_function_arguments( """Testing that function can have normal arguments.""" tree = parse_ast_tree(code.format('xy', 'normal_name')) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_wrong_name/test_function_names.py b/tests/test_visitors/test_ast/test_wrong_name/test_function_names.py similarity index 75% rename from tests/test_visitors/test_wrong_name/test_function_names.py rename to tests/test_visitors/test_ast/test_wrong_name/test_function_names.py index ea3fbc5f3..b3513a400 100644 --- a/tests/test_visitors/test_wrong_name/test_function_names.py +++ b/tests/test_visitors/test_ast/test_wrong_name/test_function_names.py @@ -4,7 +4,7 @@ import pytest -from wemake_python_styleguide.visitors.wrong_name import ( +from wemake_python_styleguide.visitors.ast.wrong_name import ( BAD_VARIABLE_NAMES, PrivateNameViolation, TooShortVariableNameViolation, @@ -33,10 +33,10 @@ def test_wrong_function_names( """Testing that function can not have blacklisted names.""" tree = parse_ast_tree(code.format(bad_name)) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [WrongVariableNameViolation]) + assert_errors(visitor, [WrongVariableNameViolation]) @pytest.mark.parametrize('short_name', string.ascii_letters) @@ -50,10 +50,10 @@ def test_too_short_function_names( """Testing that function can not have too short names.""" tree = parse_ast_tree(code.format(short_name)) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [TooShortVariableNameViolation]) + assert_errors(visitor, [TooShortVariableNameViolation]) @pytest.mark.parametrize('code', [ @@ -66,10 +66,10 @@ def test_private_function_names( """Testing that function can not have private names.""" tree = parse_ast_tree(code.format('__hidden')) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [PrivateNameViolation]) + assert_errors(visitor, [PrivateNameViolation]) @pytest.mark.parametrize('correct_name', [ @@ -89,7 +89,7 @@ def test_correct_function_names( """Testing that function can have normal names.""" tree = parse_ast_tree(code.format(correct_name)) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_wrong_name/test_import_alias.py b/tests/test_visitors/test_ast/test_wrong_name/test_import_alias.py similarity index 75% rename from tests/test_visitors/test_wrong_name/test_import_alias.py rename to tests/test_visitors/test_ast/test_wrong_name/test_import_alias.py index d86706a87..d93665198 100644 --- a/tests/test_visitors/test_wrong_name/test_import_alias.py +++ b/tests/test_visitors/test_ast/test_wrong_name/test_import_alias.py @@ -4,7 +4,7 @@ import pytest -from wemake_python_styleguide.visitors.wrong_name import ( +from wemake_python_styleguide.visitors.ast.wrong_name import ( BAD_VARIABLE_NAMES, PrivateNameViolation, TooShortVariableNameViolation, @@ -32,10 +32,10 @@ def test_wrong_import_alias_names( """Testing that import aliases can not have blacklisted names.""" tree = parse_ast_tree(code.format(bad_name)) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [WrongVariableNameViolation]) + assert_errors(visitor, [WrongVariableNameViolation]) @pytest.mark.parametrize('short_name', string.ascii_letters) @@ -49,10 +49,10 @@ def test_too_short_import_alias_names( """Testing that import aliases can not have too short names.""" tree = parse_ast_tree(code.format(short_name)) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [TooShortVariableNameViolation]) + assert_errors(visitor, [TooShortVariableNameViolation]) @pytest.mark.parametrize('code', [ @@ -65,10 +65,10 @@ def test_private_import_alias_names( """Testing that import aliases can not have too private names.""" tree = parse_ast_tree(code.format('__hidden')) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [PrivateNameViolation]) + assert_errors(visitor, [PrivateNameViolation]) @pytest.mark.parametrize('correct_name', [ @@ -87,7 +87,7 @@ def test_correct_import_alias_names( """Testing that import aliases can have normal names.""" tree = parse_ast_tree(code.format(correct_name)) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_wrong_name/test_module_metadata.py b/tests/test_visitors/test_ast/test_wrong_name/test_module_metadata.py similarity index 73% rename from tests/test_visitors/test_wrong_name/test_module_metadata.py rename to tests/test_visitors/test_ast/test_wrong_name/test_module_metadata.py index 1d1553a5f..550e4be75 100644 --- a/tests/test_visitors/test_wrong_name/test_module_metadata.py +++ b/tests/test_visitors/test_ast/test_wrong_name/test_module_metadata.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.wrong_name import ( +from wemake_python_styleguide.visitors.ast.wrong_name import ( BAD_MODULE_METADATA_VARIABLES, WrongModuleMetadataViolation, WrongModuleMetadataVisitor, @@ -33,10 +33,10 @@ def test_wrong_metadata( """Testing that metadata can not have blacklisted names.""" tree = parse_ast_tree(code.format(bad_name)) - visiter = WrongModuleMetadataVisitor(default_options) - visiter.visit(tree) + visitor = WrongModuleMetadataVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [WrongModuleMetadataViolation]) + assert_errors(visitor, [WrongModuleMetadataViolation]) @pytest.mark.parametrize('correct_name', [ @@ -54,10 +54,10 @@ def test_correct_metadata( """Testing that metadata can have normal names.""" tree = parse_ast_tree(code.format(correct_name)) - visiter = WrongModuleMetadataVisitor(default_options) - visiter.visit(tree) + visitor = WrongModuleMetadataVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) def test_correct_startup_metadata( @@ -66,7 +66,7 @@ def test_correct_startup_metadata( """Testing that startup hook is allowed.""" tree = parse_ast_tree(startup_metadata) - visiter = WrongModuleMetadataVisitor(default_options) - visiter.visit(tree) + visitor = WrongModuleMetadataVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_wrong_name/test_variable_names.py b/tests/test_visitors/test_ast/test_wrong_name/test_variable_names.py similarity index 78% rename from tests/test_visitors/test_wrong_name/test_variable_names.py rename to tests/test_visitors/test_ast/test_wrong_name/test_variable_names.py index 1ced280d8..276afe8da 100644 --- a/tests/test_visitors/test_wrong_name/test_variable_names.py +++ b/tests/test_visitors/test_ast/test_wrong_name/test_variable_names.py @@ -4,7 +4,7 @@ import pytest -from wemake_python_styleguide.visitors.wrong_name import ( +from wemake_python_styleguide.visitors.ast.wrong_name import ( BAD_VARIABLE_NAMES, PrivateNameViolation, TooShortVariableNameViolation, @@ -57,10 +57,10 @@ def test_wrong_variable_names( """Testing that variable can not have blacklisted names.""" tree = parse_ast_tree(code.format(bad_name)) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [WrongVariableNameViolation]) + assert_errors(visitor, [WrongVariableNameViolation]) @pytest.mark.parametrize('short_name', string.ascii_letters) @@ -76,10 +76,10 @@ def test_too_short_variable_names( """Testing that variable can not have too short names.""" tree = parse_ast_tree(code.format(short_name)) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [TooShortVariableNameViolation]) + assert_errors(visitor, [TooShortVariableNameViolation]) @pytest.mark.parametrize('code', [ @@ -95,10 +95,10 @@ def test_too_short_variable_names_configured( tree = parse_ast_tree(code.format('some')) option_values = options(min_variable_length=5) - visiter = WrongNameVisitor(option_values) - visiter.visit(tree) + visitor = WrongNameVisitor(option_values, tree=tree) + visitor.run() - assert_errors(visiter, [TooShortVariableNameViolation]) + assert_errors(visitor, [TooShortVariableNameViolation]) @pytest.mark.parametrize('code', [ @@ -113,10 +113,10 @@ def test_private_variable_names( """Testing that variable can not have private names.""" tree = parse_ast_tree(code.format('__private_value')) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [PrivateNameViolation]) + assert_errors(visitor, [PrivateNameViolation]) @pytest.mark.parametrize('correct_name', ['correct_name', 'xy', '_']) @@ -134,7 +134,7 @@ def test_correct_variable_name( """Testing that variable can have normal names.""" tree = parse_ast_tree(code.format(correct_name)) - visiter = WrongNameVisitor(default_options) - visiter.visit(tree) + visitor = WrongNameVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_wrong_string.py b/tests/test_visitors/test_ast/test_wrong_string.py similarity index 75% rename from tests/test_visitors/test_wrong_string.py rename to tests/test_visitors/test_ast/test_wrong_string.py index 1c15b235d..6d063be61 100644 --- a/tests/test_visitors/test_wrong_string.py +++ b/tests/test_visitors/test_ast/test_wrong_string.py @@ -2,7 +2,7 @@ import pytest -from wemake_python_styleguide.visitors.wrong_string import ( +from wemake_python_styleguide.visitors.ast.wrong_string import ( FormattedStringViolation, WrongStringVisitor, ) @@ -33,10 +33,10 @@ def test_string_normal( """Testing that regular strings work well.""" tree = parse_ast_tree(code) - visiter = WrongStringVisitor(default_options) - visiter.visit(tree) + visitor = WrongStringVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, []) + assert_errors(visitor, []) @pytest.mark.parametrize('code', [ @@ -47,7 +47,7 @@ def test_wrong_string(assert_errors, parse_ast_tree, code, default_options): """Testing that violations are raised when reaching max value.""" tree = parse_ast_tree(code) - visiter = WrongStringVisitor(default_options) - visiter.visit(tree) + visitor = WrongStringVisitor(default_options, tree=tree) + visitor.run() - assert_errors(visiter, [FormattedStringViolation]) + assert_errors(visitor, [FormattedStringViolation]) diff --git a/tests/test_visitors/test_base.py b/tests/test_visitors/test_base.py new file mode 100644 index 000000000..1bb9b3cb5 --- /dev/null +++ b/tests/test_visitors/test_base.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +from unittest.mock import MagicMock + +import pytest + +from wemake_python_styleguide import constants +from wemake_python_styleguide.visitors.base import ( + BaseChecker, + BaseFilenameVisitor, + BaseNodeVisitor, +) + + +def test_raises_value_error_without_tree(default_options): + """Ensures that ValueError is raised when visitor does not have a tree.""" + with pytest.raises(ValueError): + BaseNodeVisitor(default_options).run() + + +def test_checker_raises_not_implemented(default_options): + """Ensures that `BaseChecker` raises `NotImplementedError`.""" + with pytest.raises(NotImplementedError): + BaseChecker(default_options).run() + + +def test_base_filename_raises_not_implemented(default_options): + """Ensures that `BaseFilenameVisitor` raises `NotImplementedError`.""" + with pytest.raises(NotImplementedError): + BaseFilenameVisitor(default_options, filename='some.py').run() + + +def test_base_filename_run_do_not_call_visit(default_options): + """Ensures that `run()` does not call `visit()` method for stdin.""" + instance = BaseFilenameVisitor(default_options, filename=constants.STDIN) + instance.visit_filename = MagicMock() + instance.run() + + instance.visit_filename.assert_not_called() diff --git a/tests/test_visitors/test_filenames/test_wrong_module_name/test_module_magic_name.py b/tests/test_visitors/test_filenames/test_wrong_module_name/test_module_magic_name.py new file mode 100644 index 000000000..82653448e --- /dev/null +++ b/tests/test_visitors/test_filenames/test_wrong_module_name/test_module_magic_name.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +import pytest + +from wemake_python_styleguide.constants import MAGIC_MODULE_NAMES_WHITELIST +from wemake_python_styleguide.visitors.filenames.wrong_module_name import ( + WrongModuleMagicNameViolation, + WrongModuleNameVisitor, +) + + +@pytest.mark.parametrize('filename', MAGIC_MODULE_NAMES_WHITELIST) +def test_correct_magic_filename(assert_errors, filename, default_options): + """Testing that allowed magic file names works well.""" + visitor = WrongModuleNameVisitor(default_options, filename=filename) + visitor.run() + + assert_errors(visitor, []) + + +@pytest.mark.parametrize('filename', [ + '__version__.py', + '__custom__.py', + '____.py', +]) +def test_simple_filename(assert_errors, filename, default_options): + """Testing that some file names are restricted.""" + visitor = WrongModuleNameVisitor(default_options, filename=filename) + visitor.run() + + assert_errors(visitor, [WrongModuleMagicNameViolation]) diff --git a/tests/test_visitors/test_filenames/test_wrong_module_name/test_module_name.py b/tests/test_visitors/test_filenames/test_wrong_module_name/test_module_name.py new file mode 100644 index 000000000..1b1941568 --- /dev/null +++ b/tests/test_visitors/test_filenames/test_wrong_module_name/test_module_name.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +import pytest + +from wemake_python_styleguide.constants import BAD_MODULE_NAMES +from wemake_python_styleguide.visitors.filenames.wrong_module_name import ( + WrongModuleNameViolation, + WrongModuleNameVisitor, +) + + +@pytest.mark.parametrize('filename', [ + 'query.py', + '/home/user/logics.py', + 'partial/views.py', + 'C:/path/package/module.py', +]) +def test_simple_filename(assert_errors, filename, default_options): + """Testing that simple file names works well.""" + visitor = WrongModuleNameVisitor(default_options, filename=filename) + visitor.run() + + assert_errors(visitor, []) + + +@pytest.mark.parametrize('filename', BAD_MODULE_NAMES) +def test_restricted_filename(assert_errors, filename, default_options): + """Testing that some file names are restricted.""" + visitor = WrongModuleNameVisitor(default_options, filename=filename + '.py') + visitor.run() + + assert_errors(visitor, [WrongModuleNameViolation]) diff --git a/tests/test_visitors/test_filenames/test_wrong_module_name/test_module_name_length.py b/tests/test_visitors/test_filenames/test_wrong_module_name/test_module_name_length.py new file mode 100644 index 000000000..b028455d0 --- /dev/null +++ b/tests/test_visitors/test_filenames/test_wrong_module_name/test_module_name_length.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +import pytest + +from wemake_python_styleguide.visitors.filenames.wrong_module_name import ( + TooShortModuleNameViolation, + WrongModuleNameVisitor, +) + + +@pytest.mark.parametrize('filename', [ + 'a.py', + 'some/package/z.py', + '/root/x.py', + 'C:/f.py', +]) +def test_too_short_filename(assert_errors, filename, default_options): + """Testing that short file names are restricted.""" + visitor = WrongModuleNameVisitor(default_options, filename=filename) + visitor.run() + + assert_errors(visitor, [TooShortModuleNameViolation]) + + +def test_length_option(assert_errors, options): + """Ensures that option `--min-module-name-length` works.""" + option_values = options(min_module_name_length=5) + visitor = WrongModuleNameVisitor(option_values, filename='test.py') + visitor.run() + + assert_errors(visitor, [TooShortModuleNameViolation]) diff --git a/tests/test_visitors/test_wrong_import/test_relative_imports.py b/tests/test_visitors/test_wrong_import/test_relative_imports.py deleted file mode 100644 index 4bf8daff5..000000000 --- a/tests/test_visitors/test_wrong_import/test_relative_imports.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- - -import pytest - -from wemake_python_styleguide.visitors.wrong_import import ( - LocalFolderImportViolation, - WrongImportVisitor, -) - -same_level_relative_import = """ -from . import some -""" - -same_level_relative_import_sibling = """ -from .some import MyClass -""" - -parent_level_relative_import = """ -from .. import some -""" - -parent_level_relative_import_sibling = """ -from ..some import MyClass -""" - - -@pytest.mark.parametrize('code', [ - same_level_relative_import, - same_level_relative_import_sibling, - parent_level_relative_import, - parent_level_relative_import_sibling, -]) -def test_local_folder_import( - assert_errors, parse_ast_tree, code, default_options, -): - """Testing that relative to local folder imports are restricted.""" - tree = parse_ast_tree(code) - - visiter = WrongImportVisitor(default_options) - visiter.visit(tree) - - assert_errors(visiter, [LocalFolderImportViolation]) - - -def test_regular_import(assert_errors, parse_ast_tree, default_options): - """Testing that regular imports are allowed.""" - tree = parse_ast_tree('import os') - - visiter = WrongImportVisitor(default_options) - visiter.visit(tree) - - assert_errors(visiter, []) - - -def test_regular_from_import(assert_errors, parse_ast_tree, default_options): - """Testing that regular from imports are allowed.""" - tree = parse_ast_tree('from os import path') - - visiter = WrongImportVisitor(default_options) - visiter.visit(tree) - - assert_errors(visiter, []) diff --git a/wemake_python_styleguide/checker.py b/wemake_python_styleguide/checker.py index dc66d69c5..d4be65340 100644 --- a/wemake_python_styleguide/checker.py +++ b/wemake_python_styleguide/checker.py @@ -5,41 +5,55 @@ from flake8.options.manager import OptionManager +from wemake_python_styleguide import constants from wemake_python_styleguide.compat import maybe_set_parent from wemake_python_styleguide.options.config import Configuration from wemake_python_styleguide.types import ( + CheckerSequence, CheckResult, ConfigurationOptions, - VisitorSequence, ) from wemake_python_styleguide.version import version -from wemake_python_styleguide.visitors.complexity.counts import ( +from wemake_python_styleguide.visitors.ast.complexity.counts import ( MethodMembersVisitor, ModuleMembersVisitor, ) -from wemake_python_styleguide.visitors.complexity.function import ( +from wemake_python_styleguide.visitors.ast.complexity.function import ( FunctionComplexityVisitor, ) -from wemake_python_styleguide.visitors.complexity.nested import ( +from wemake_python_styleguide.visitors.ast.complexity.nested import ( NestedComplexityVisitor, ) -from wemake_python_styleguide.visitors.complexity.offset import OffsetVisitor -from wemake_python_styleguide.visitors.wrong_class import WrongClassVisitor -from wemake_python_styleguide.visitors.wrong_function_call import ( +from wemake_python_styleguide.visitors.ast.complexity.offset import ( + OffsetVisitor, +) +from wemake_python_styleguide.visitors.ast.wrong_class import WrongClassVisitor +from wemake_python_styleguide.visitors.ast.wrong_contents import ( + WrongContentsVisitor, +) +from wemake_python_styleguide.visitors.ast.wrong_function_call import ( WrongFunctionCallVisitor, ) -from wemake_python_styleguide.visitors.wrong_import import WrongImportVisitor -from wemake_python_styleguide.visitors.wrong_keyword import ( +from wemake_python_styleguide.visitors.ast.wrong_import import ( + WrongImportVisitor, +) +from wemake_python_styleguide.visitors.ast.wrong_keyword import ( WrongKeywordVisitor, WrongRaiseVisitor, ) -from wemake_python_styleguide.visitors.wrong_name import ( +from wemake_python_styleguide.visitors.ast.wrong_name import ( WrongModuleMetadataVisitor, WrongNameVisitor, ) +from wemake_python_styleguide.visitors.ast.wrong_string import ( + WrongStringVisitor, +) +from wemake_python_styleguide.visitors.filenames.wrong_module_name import ( + WrongModuleNameVisitor, +) #: Visitors that should be working by default: -ENABLED_VISITORS: VisitorSequence = [ +ENABLED_VISITORS: CheckerSequence = [ # Styling and correctness: WrongRaiseVisitor, WrongFunctionCallVisitor, @@ -48,6 +62,8 @@ WrongNameVisitor, WrongModuleMetadataVisitor, WrongClassVisitor, + WrongStringVisitor, + WrongContentsVisitor, # Complexity: FunctionComplexityVisitor, @@ -55,6 +71,9 @@ OffsetVisitor, ModuleMembersVisitor, MethodMembersVisitor, + + # Modules: + WrongModuleNameVisitor, ] @@ -72,19 +91,19 @@ class Checker(object): config = Configuration() options: ConfigurationOptions - def __init__(self, tree: Module, filename: str = 'stdin') -> None: + def __init__(self, tree: Module, filename: str = constants.STDIN) -> None: """Creates new checker instance.""" self.tree = maybe_set_parent(tree) self.filename = filename @classmethod - def add_options(cls, parser: OptionManager): + def add_options(cls, parser: OptionManager) -> None: """Calls Configuration instance method for registering options.""" cls.config.register_options(parser) @classmethod - def parse_options(cls, options: ConfigurationOptions): - """Parses registered options for providing to the visiter.""" + def parse_options(cls, options: ConfigurationOptions) -> None: + """Parses registered options for providing to the visitor.""" cls.options = options def run(self) -> Generator[CheckResult, None, None]: @@ -95,8 +114,12 @@ def run(self) -> Generator[CheckResult, None, None]: After all configuration is parsed and passed. """ for visitor_class in ENABLED_VISITORS: - visiter = visitor_class(self.options, filename=self.filename) - visiter.visit(self.tree) - - for error in visiter.errors: + visitor = visitor_class( + self.options, + tree=self.tree, + filename=self.filename, + ) + visitor.run() + + for error in visitor.errors: yield (*error.node_items(), type(self)) diff --git a/wemake_python_styleguide/constants.py b/wemake_python_styleguide/constants.py index d7f5e7e71..425b194d3 100644 --- a/wemake_python_styleguide/constants.py +++ b/wemake_python_styleguide/constants.py @@ -10,8 +10,6 @@ import sys from typing import Tuple -# TODO: 'stdin' filename should be a constant - #: List of functions we forbid to use. BAD_FUNCTIONS = frozenset(( # Code generation: @@ -70,7 +68,9 @@ 'handle', 'handler', 'file', - 'klass', + 'obj', + 'objects', + 'objs', 'foo', 'bar', 'baz', @@ -113,3 +113,27 @@ 'annotations', 'generator_stop', )) + +#: List of blacklisted module names: +BAD_MODULE_NAMES = frozenset(( + 'util', + 'utils', + 'utilities', + 'helpers', +)) + +#: List of allowed module magic names: +MAGIC_MODULE_NAMES_WHITELIST = frozenset(( + '__init__', + '__main__', +)) + + +# Internal variables +# They are not publicly documented since they are only used internally + +# This variable is used as a default filename, when it is not passed by flake8: +STDIN = 'stdin' + +# This variable is used to specify as a placeholder for `__init__.py`: +INIT = '__init__' diff --git a/wemake_python_styleguide/errors/base.py b/wemake_python_styleguide/errors/base.py index e4b5bb702..1d06eefaf 100644 --- a/wemake_python_styleguide/errors/base.py +++ b/wemake_python_styleguide/errors/base.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from ast import AST -from typing import Tuple +import ast +from typing import Optional, Tuple class BaseStyleViolation(object): @@ -11,15 +11,16 @@ class BaseStyleViolation(object): It basically just defines how to create any error and how to format this error later on. - Each subclass must define ``_error_tmpl`` and ``_code`` fields. + Each subclass must define ``error_template`` and ``code`` fields. """ - _error_tmpl: str - _code: str + error_template: str + code: str + should_use_text: bool = True - def __init__(self, node: AST, text: str = None) -> None: - """Creates new instance of style error.""" - self.node = node + def __init__(self, node: Optional[ast.AST], text: str = None) -> None: + """Creates new instance of AST style violation.""" + self._node = node if text is None: self._text = node.__class__.__name__.lower() @@ -27,26 +28,29 @@ def __init__(self, node: AST, text: str = None) -> None: self._text = text def message(self) -> str: - """ - Returns error's formated message. + """Returns error's formatted message.""" + if self.should_use_text: + return self.error_template.format(self.code, self._text) + return self.error_template.format(self.code) - >>> import ast - >>> from wemake_python_styleguide.errors.general import ( - ... WrongKeywordViolation, - ... ) - >>> error = WrongKeywordViolation(ast.Pass()) - >>> error.message() - 'Z110 Found wrong keyword "pass"' + def node_items(self) -> Tuple[int, int, str]: + """Returns `Tuple` to match `flake8` API format.""" + line_number = getattr(self._node, 'lineno', 0) + column_offset = getattr(self._node, 'col_offset', 0) + return line_number, column_offset, self.message() - >>> error = WrongKeywordViolation(ast.Delete(), text='del') - >>> error.message() - 'Z110 Found wrong keyword "del"' - """ - return self._error_tmpl.format(self._code, self._text) +class ASTStyleViolation(BaseStyleViolation): + """AST based style violations.""" - def node_items(self) -> Tuple[int, int, str]: - """Returns `Tuple` to match `flake8` API format.""" - lineno = getattr(self.node, 'lineno', 0) - col_offset = getattr(self.node, 'col_offset', 0) - return lineno, col_offset, self.message() + def __init__(self, node: ast.AST, text: str = None) -> None: + """Creates new instance of AST style violation.""" + super().__init__(node, text=text) + + +class SimpleStyleViolation(BaseStyleViolation): + """Style violation for cases where there's no AST nodes.""" + + def __init__(self, node=None, text: str = None) -> None: + """Creates new instance of simple style violation.""" + super().__init__(node, text=text) diff --git a/wemake_python_styleguide/errors/classes.py b/wemake_python_styleguide/errors/classes.py index 706aba4ad..861dffb4e 100644 --- a/wemake_python_styleguide/errors/classes.py +++ b/wemake_python_styleguide/errors/classes.py @@ -1,11 +1,18 @@ # -*- coding: utf-8 -*- -"""These rules checks ``class``es to be defined correctly.""" +""" +These rules checks that ``class``es are defined correctly. -from wemake_python_styleguide.errors.base import BaseStyleViolation +Beautiful is better than ugly. +Explicit is better than implicit. +In the face of ambiguity, refuse the temptation to guess. +There should be one-- and preferably only one --obvious way to do it. +""" +from wemake_python_styleguide.errors.base import ASTStyleViolation -class StaticMethodViolation(BaseStyleViolation): + +class StaticMethodViolation(ASTStyleViolation): """ This rule forbids to use ``@staticmethod`` decorator. @@ -16,24 +23,27 @@ class StaticMethodViolation(BaseStyleViolation): """ - _error_tmpl = '{0} Found using staticmethod "{1}"' - _code = 'Z300' + should_use_text = False + error_template = '{0} Found using `@staticmethod`' + code = 'Z300' -class BadMagicMethodViolation(BaseStyleViolation): +class BadMagicMethodViolation(ASTStyleViolation): """ This rule forbids to use some magic methods. + See ``BAD_MAGIC_METHODS`` for the full blacklist of the magic methods. + Note: Returns Z301 as error code """ - _error_tmpl = '{0} Found using restricted magic method "{1}"' - _code = 'Z301' + error_template = '{0} Found using restricted magic method "{1}"' + code = 'Z301' -class RequiredBaseClassViolation(BaseStyleViolation): +class RequiredBaseClassViolation(ASTStyleViolation): """ This rule forbids to write classes without base classes. @@ -50,5 +60,5 @@ class Some: ... """ - _error_tmpl = '{0} Found class without a base class "{1}"' - _code = 'Z302' + error_template = '{0} Found class without a base class "{1}"' + code = 'Z302' diff --git a/wemake_python_styleguide/errors/complexity.py b/wemake_python_styleguide/errors/complexity.py index ec6b7af38..e7369a768 100644 --- a/wemake_python_styleguide/errors/complexity.py +++ b/wemake_python_styleguide/errors/complexity.py @@ -4,14 +4,19 @@ These checks finds flaws in your application design. What we call "design flaws": + 1. Complex code (there are a lof of complexity checks!) 2. Nested classes, functions + +Simple is better than complex. +Complex is better than complicated. +Flat is better than nested. """ -from wemake_python_styleguide.errors.base import BaseStyleViolation +from wemake_python_styleguide.errors.base import ASTStyleViolation -class NestedFunctionViolation(BaseStyleViolation): +class NestedFunctionViolation(ASTStyleViolation): """ This rule forbids to have nested functions. @@ -37,22 +42,27 @@ def inner(): """ - _error_tmpl = '{0} Found nested function "{1}"' - _code = 'Z200' + error_template = '{0} Found nested function "{1}"' + code = 'Z200' -class NestedClassViolation(BaseStyleViolation): +class NestedClassViolation(ASTStyleViolation): """ This rule forbids to have nested classes. Just write flat classes, there's no need nest them. However, there are some whitelisted class names like: ``Meta``. + See ``NESTED_CLASSES_WHITELIST`` for the full list of names. Example:: + # Correct: + class Some(object): ... + class Other(object): ... + # Wrong: - class Some: - class Inner: + class Some(object): + class Inner(object): ... Note: @@ -60,11 +70,11 @@ class Inner: """ - _error_tmpl = '{0} Found nested class "{1}"' - _code = 'Z201' + error_template = '{0} Found nested class "{1}"' + code = 'Z201' -class TooManyLocalsViolation(BaseStyleViolation): +class TooManyLocalsViolation(ASTStyleViolation): """ This rule forbids to have too many local variables in the unit of code. @@ -92,16 +102,18 @@ def second_function(argument): Please, note that `_` is a special case. It is not counted as a local variable. Since by design it means: do not count me as a real variable. + This rule is configurable with ``--max-local-variables``. + Note: Returns Z202 as error code """ - _error_tmpl = '{0} Found too many local variables "{1}"' - _code = 'Z202' + error_template = '{0} Found too many local variables "{1}"' + code = 'Z202' -class TooManyArgumentsViolation(BaseStyleViolation): +class TooManyArgumentsViolation(ASTStyleViolation): """ This rule forbids to have too many arguments for a function or method. @@ -109,16 +121,18 @@ class TooManyArgumentsViolation(BaseStyleViolation): When function requires many arguments it shows that it is required to refactor this piece of code. + This rule is configurable with ``--max-arguments``. + Note: Returns Z203 as error code """ - _error_tmpl = '{0} Found too many arguments "{1}"' - _code = 'Z203' + error_template = '{0} Found too many arguments "{1}"' + code = 'Z203' -class TooManyElifsViolation(BaseStyleViolation): +class TooManyElifsViolation(ASTStyleViolation): """ This rule forbids to use many `elif` branches. @@ -127,32 +141,36 @@ class TooManyElifsViolation(BaseStyleViolation): There are different design patters to use instead. + This rule is configurable with ``--max-elifs``. + Note: Returns Z204 as error code """ - _error_tmpl = '{0} Found too many "{1}" branches' - _code = 'Z204' + error_template = '{0} Found too many "{1}" branches' + code = 'Z204' -class TooManyReturnsViolation(BaseStyleViolation): +class TooManyReturnsViolation(ASTStyleViolation): """ This rule forbids placing too many ``return`` statements into the function. When there are too many ``return`` keywords, functions are hard to test. They are also hard to read and hard to change and read. + This rule is configurable with ``--max-returns``. + Note: Returns Z205 as error code """ - _error_tmpl = '{0} Found too many return statements "{1}"' - _code = 'Z205' + error_template = '{0} Found too many return statements "{1}"' + code = 'Z205' -class TooManyExpressionsViolation(BaseStyleViolation): +class TooManyExpressionsViolation(ASTStyleViolation): """ This rule forbids putting to many expression is a unit of code. @@ -160,16 +178,18 @@ class TooManyExpressionsViolation(BaseStyleViolation): some logical or structural problems. We only have to identify them. + This rule is configurable with ``--max-expressions``. + Note: Returns Z206 as error code """ - _error_tmpl = '{0} Found too many expressions "{1}"' - _code = 'Z206' + error_template = '{0} Found too many expressions "{1}"' + code = 'Z206' -class TooDeepNestingViolation(BaseStyleViolation): +class TooDeepNestingViolation(ASTStyleViolation): """ This rule forbids nesting blocks too deep. @@ -178,16 +198,18 @@ class TooDeepNestingViolation(BaseStyleViolation): So, we need to check these cases before they have made their way to production. + This rule is configurable with ``--max-offset-blocks``. + Note: Returns Z207 as error code """ - _error_tmpl = '{0} Found too deep nesting "{1}"' - _code = 'Z207' + error_template = '{0} Found too deep nesting "{1}"' + code = 'Z207' -class TooManyModuleMembersViolation(BaseStyleViolation): +class TooManyModuleMembersViolation(ASTStyleViolation): """ This rule forbids to have many classes and functions in a single module. @@ -199,18 +221,20 @@ class TooManyModuleMembersViolation(BaseStyleViolation): We do not make any differences between classes and functions in this check. They are treated as the same unit of logic. We also do no care about functions and classes been public or not. - However, methods are counted separatelly on a per-class basis. + However, methods are counted separately on a per-class basis. + + This rule is configurable with ``--max-module-members``. Note: Returns Z208 as error code """ - _error_tmpl = '{0} Found too many members "{1}"' - _code = 'Z208' + error_template = '{0} Found too many members "{1}"' + code = 'Z208' -class TooManyMethodsViolation(BaseStyleViolation): +class TooManyMethodsViolation(ASTStyleViolation): """ This rule forbids to have many methods in a single class. @@ -226,10 +250,12 @@ class TooManyMethodsViolation(BaseStyleViolation): This rule do not count attributes of a class. + This rule is configurable with ``--max-methods``. + Note: Returns Z209 as error code """ - _error_tmpl = '{0} Found too many methods "{1}"' - _code = 'Z209' + error_template = '{0} Found too many methods "{1}"' + code = 'Z209' diff --git a/wemake_python_styleguide/errors/general.py b/wemake_python_styleguide/errors/general.py index 8934093f6..05166d80b 100644 --- a/wemake_python_styleguide/errors/general.py +++ b/wemake_python_styleguide/errors/general.py @@ -10,12 +10,16 @@ 3. Using keywords 4. Working with exceptions +Beautiful is better than ugly. +Explicit is better than implicit. +In the face of ambiguity, refuse the temptation to guess. +There should be one-- and preferably only one --obvious way to do it. """ -from wemake_python_styleguide.errors.base import BaseStyleViolation +from wemake_python_styleguide.errors.base import ASTStyleViolation -class WrongKeywordViolation(BaseStyleViolation): +class WrongKeywordViolation(ASTStyleViolation): """ This rule forbids to use some keywords from ``python``. @@ -25,19 +29,20 @@ class WrongKeywordViolation(BaseStyleViolation): # Wrong: pass - exec - eval + del + nonlocal + global Note: Returns Z110 as error code """ - _error_tmpl = '{0} Found wrong keyword "{1}"' - _code = 'Z110' + error_template = '{0} Found wrong keyword "{1}"' + code = 'Z110' -class BareRiseViolation(BaseStyleViolation): +class BareRiseViolation(ASTStyleViolation): """ This rule forbids using bare `raise` keyword outside of `except` block. @@ -57,11 +62,11 @@ class BareRiseViolation(BaseStyleViolation): """ - _error_tmpl = '{0} Found bare raise outside of except "{1}"' - _code = 'Z111' + error_template = '{0} Found bare raise outside of except "{1}"' + code = 'Z111' -class RaiseNotImplementedViolation(BaseStyleViolation): +class RaiseNotImplementedViolation(ASTStyleViolation): """ This rule forbids to use `NotImplemented` error. @@ -84,30 +89,34 @@ class RaiseNotImplementedViolation(BaseStyleViolation): """ - _error_tmpl = '{0} Found raise NotImplemented "{1}"' - _code = 'Z112' + error_template = '{0} Found raise NotImplemented "{1}"' + code = 'Z112' -class WrongFunctionCallViolation(BaseStyleViolation): +class WrongFunctionCallViolation(ASTStyleViolation): """ This rule forbids to call some built-in functions. Since some functions are only suitable for very specific usecases, we forbid to use them in a free manner. + See ``BAD_FUNCTIONS`` for the full list of blacklisted functions. + Note: Returns Z113 as error code """ - _error_tmpl = '{0} Found wrong function call "{1}"' - _code = 'Z113' + error_template = '{0} Found wrong function call "{1}"' + code = 'Z113' -class WrongVariableNameViolation(BaseStyleViolation): +class WrongVariableNameViolation(ASTStyleViolation): """ This rule forbids to have blacklisted variable names. + See ``BAD_VARIABLE_NAMES`` for the full list of blacklisted variable names. + Example:: # Correct: @@ -121,14 +130,16 @@ class WrongVariableNameViolation(BaseStyleViolation): """ - _error_tmpl = '{0} Found wrong variable name "{1}"' - _code = 'Z114' + error_template = '{0} Found wrong variable name "{1}"' + code = 'Z114' -class TooShortVariableNameViolation(BaseStyleViolation): +class TooShortVariableNameViolation(ASTStyleViolation): """ This rule forbids to have too short variable names. + This rule is configurable with ``--min-variable-length``. + Example:: # Correct: @@ -142,11 +153,11 @@ class TooShortVariableNameViolation(BaseStyleViolation): """ - _error_tmpl = '{0} Found too short name "{1}"' - _code = 'Z115' + error_template = '{0} Found too short name "{1}"' + code = 'Z115' -class PrivateNameViolation(BaseStyleViolation): +class PrivateNameViolation(ASTStyleViolation): """ This rule forbids to have private name pattern. @@ -156,6 +167,7 @@ class PrivateNameViolation(BaseStyleViolation): # Correct: def _collect_coverage(self): ... + # Wrong: def __collect_coverage(self): ... @@ -164,11 +176,11 @@ def __collect_coverage(self): ... """ - _error_tmpl = '{0} Found private name pattern "{1}"' - _code = 'Z116' + error_template = '{0} Found private name pattern "{1}"' + code = 'Z116' -class WrongModuleMetadataViolation(BaseStyleViolation): +class WrongModuleMetadataViolation(ASTStyleViolation): """ This rule forbids to have some module level variables. @@ -176,6 +188,8 @@ class WrongModuleMetadataViolation(BaseStyleViolation): there's no need in them. Use proper docstrings and classifiers. Packaging should not be done in code. + See ``BAD_MODULE_METADATA_VARIABLES`` for full list of bad names. + Example:: # Wrong: @@ -186,14 +200,18 @@ class WrongModuleMetadataViolation(BaseStyleViolation): """ - _error_tmpl = '{0} Found wrong metadata variable {1}' - _code = 'Z117' + error_template = '{0} Found wrong metadata variable {1}' + code = 'Z117' -class FormattedStringViolation(BaseStyleViolation): +class FormattedStringViolation(ASTStyleViolation): """ This rule forbids to use `f` strings. + `f` strings looses context to often, they are hard to lint. + Also, they promote a bad practice: putting a logic into the template. + Use `.format()` instead. + Example:: # Wrong: @@ -207,5 +225,50 @@ class FormattedStringViolation(BaseStyleViolation): """ - _error_tmpl = '{0} Found `f` string {1}' - _code = 'Z118' + should_use_text = False + error_template = '{0} Found `f` string' + code = 'Z118' + + +class EmptyModuleViolation(ASTStyleViolation): + """ + This rule forbids to have empty modules. + + If you have an empty module there are two ways to handle that: + + 1. delete it, why is it even there? + 2. drop some documentation in it, so you will explain why it is there + + Note: + Returns Z119 as error code + + """ + + should_use_text = False + error_template = '{0} Found empty module' + code = 'Z119' + + +class InitModuleHasLogicViolation(ASTStyleViolation): + """ + This rule forbids to have logic inside `__init__` module. + + If you have logic inside the `__init__` module it means several things: + + 1. you are keeping some outdated stuff there, you need to refactor + 2. you are placing this logic into the wrong file, just create another one + 3. you are doing some dark magic, and you should not do that + + However, we allow to have some contents inside the `__init__` module: + + 1. comments, since they are dropped before AST comes in play + 2. docs string, because sometimes it is required to state something + + Note: + Returns Z120 as error code + + """ + + should_use_text = False + error_template = '{0} Found `__init__` module with logic' + code = 'Z120' diff --git a/wemake_python_styleguide/errors/imports.py b/wemake_python_styleguide/errors/imports.py index 73f5852de..0757cf621 100644 --- a/wemake_python_styleguide/errors/imports.py +++ b/wemake_python_styleguide/errors/imports.py @@ -1,11 +1,18 @@ # -*- coding: utf-8 -*- -"""These rules checks ``import``s to be defined correctly.""" +""" +These rules checks ``import``s to be defined correctly. -from wemake_python_styleguide.errors.base import BaseStyleViolation +Explicit is better than implicit. +Flat is better than nested. +Sparse is better than dense. +Readability counts. +""" +from wemake_python_styleguide.errors.base import ASTStyleViolation -class LocalFolderImportViolation(BaseStyleViolation): + +class LocalFolderImportViolation(ASTStyleViolation): """ This rule forbids to have imports relative to the current folder. @@ -23,11 +30,11 @@ class LocalFolderImportViolation(BaseStyleViolation): """ - _error_tmpl = '{0} Found local folder import "{1}"' - _code = 'Z100' + error_template = '{0} Found local folder import "{1}"' + code = 'Z100' -class NestedImportViolation(BaseStyleViolation): +class NestedImportViolation(ASTStyleViolation): """ This rule forbids to have nested imports in functions. @@ -36,6 +43,11 @@ class NestedImportViolation(BaseStyleViolation): Example:: + # Correct: + from my_module import some_function + + def some(): ... + # Wrong: def some(): from my_module import some_function @@ -45,11 +57,11 @@ def some(): """ - _error_tmpl = '{0} Found nested import "{1}"' - _code = 'Z101' + error_template = '{0} Found nested import "{1}"' + code = 'Z101' -class FutureImportViolation(BaseStyleViolation): +class FutureImportViolation(ASTStyleViolation): """ This rule forbids to use ``__future__`` imports. @@ -57,6 +69,8 @@ class FutureImportViolation(BaseStyleViolation): tools that are no longer required. Except, there are some new ones for ``python4`` support. + See ``FUTURE_IMPORTS_WHITELIST`` for the full + list of allowed future imports. Example:: @@ -71,11 +85,11 @@ class FutureImportViolation(BaseStyleViolation): """ - _error_tmpl = '{0} Found future import "{1}"' - _code = 'Z102' + error_template = '{0} Found future import "{1}"' + code = 'Z102' -class DottedRawImportViolation(BaseStyleViolation): +class DottedRawImportViolation(ASTStyleViolation): """ This rule forbids to use imports like ``import os.path``. @@ -92,11 +106,11 @@ class DottedRawImportViolation(BaseStyleViolation): """ - _error_tmpl = '{0} Found dotted raw import "{1}"' - _code = 'Z103' + error_template = '{0} Found dotted raw import "{1}"' + code = 'Z103' -class SameAliasImportViolation(BaseStyleViolation): +class SameAliasImportViolation(ASTStyleViolation): """ This rule forbids to use the same alias as the original name in imports. @@ -113,5 +127,5 @@ class SameAliasImportViolation(BaseStyleViolation): """ - _error_tmpl = '{0} Found same alias import "{1}"' - _code = 'Z104' + error_template = '{0} Found same alias import "{1}"' + code = 'Z104' diff --git a/wemake_python_styleguide/errors/modules.py b/wemake_python_styleguide/errors/modules.py new file mode 100644 index 000000000..f3aa53376 --- /dev/null +++ b/wemake_python_styleguide/errors/modules.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +""" +These rules checks that modules are defined correctly. + +Please, take a note that these rules are not applied to packages. +""" + +from wemake_python_styleguide.errors.base import SimpleStyleViolation + + +class WrongModuleNameViolation(SimpleStyleViolation): + """ + This rule forbids to use blacklisted module names. + + See ``BAD_MODULE_NAMES`` for the full list of bad module names. + + Example:: + + # Correct: + github.py + views.py + + # Wrong: + utils.py + helpers.py + + Note: + Returns Z400 as error code + + """ + + should_use_text = False + error_template = '{0} Found wrong module name' + code = 'Z400' + + +class WrongModuleMagicNameViolation(SimpleStyleViolation): + """ + This rule forbids to use any magic names except whitelisted ones. + + See ``MAGIC_MODULE_NAMES_WHITELIST`` for the full list of allowed + magic module names. + + Example:: + + # Correct: + __init__.py + __main__.py + + # Wrong: + __version__.py + + Note: + Returns Z401 as error code + + """ + + should_use_text = False + error_template = '{0} Found wrong module magic name' + code = 'Z401' + + +class TooShortModuleNameViolation(SimpleStyleViolation): + """ + This rule forbids to use module name shorter than some breakpoint. + + Too short module names are not expressive enough. + We will have to open the source code to find out what is going on there. + + This rule is configurable with ``--min-module-name-length``. + + Note: + Returns Z402 as error code + + """ + + should_use_text = False + error_template = '{0} Found too short module name' + code = 'Z402' diff --git a/wemake_python_styleguide/logics/filenames.py b/wemake_python_styleguide/logics/filenames.py new file mode 100644 index 000000000..e1993f40b --- /dev/null +++ b/wemake_python_styleguide/logics/filenames.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- + +from pathlib import PurePath +from typing import Iterable + +from wemake_python_styleguide.options.defaults import MIN_MODULE_NAME_LENGTH + + +def _get_stem(file_path: str) -> str: + return PurePath(file_path).stem + + +def is_stem_in_list(file_path: str, to_check: Iterable[str]) -> bool: + """ + Checks either module's name is included in a search list. + + >>> is_stem_in_list('/some/module.py', ['other']) + False + + >>> is_stem_in_list('partial/module.py', ['module']) + True + + >>> is_stem_in_list('module.py', ['module']) + True + + >>> is_stem_in_list('C:/User/package/__init__.py', ['__init__']) + True + + """ + return _get_stem(file_path) in to_check + + +def is_magic(file_path: str) -> bool: + """ + Checks either the given `file_path` contains the magic module name. + + >>> is_magic('__init__.py') + True + + >>> is_magic('some.py') + False + + >>> is_magic('/home/user/cli.py') + False + + >>> is_magic('/home/user/__version__.py') + True + + >>> is_magic('D:/python/__main__.py') + True + + """ + stem = _get_stem(file_path) + return stem.startswith('__') and stem.endswith('__') + + +def is_too_short_stem( + file_path: str, + min_length: int = MIN_MODULE_NAME_LENGTH, +) -> bool: + """ + Checks either the file's stem fits into the minimum length. + + >>> is_too_short_stem('a.py') + True + + >>> is_too_short_stem('prefix/b.py') + True + + >>> is_too_short_stem('regular.py') + False + + >>> is_too_short_stem('c:/package/abc.py', min_length=4) + True + + """ + stem = _get_stem(file_path) + return len(stem) < min_length diff --git a/wemake_python_styleguide/logics/variables.py b/wemake_python_styleguide/logics/variables.py index ae1579adb..149b18613 100644 --- a/wemake_python_styleguide/logics/variables.py +++ b/wemake_python_styleguide/logics/variables.py @@ -2,6 +2,8 @@ from typing import Iterable, Optional +from wemake_python_styleguide.options.defaults import MIN_VARIABLE_LENGTH + def is_wrong_variable_name(name: str, to_check: Iterable[str]) -> bool: """ @@ -34,7 +36,7 @@ def is_wrong_variable_name(name: str, to_check: Iterable[str]) -> bool: def is_too_short_variable_name( name: Optional[str], - min_length: int = 2, + min_length: int = MIN_VARIABLE_LENGTH, ) -> bool: """ Checks for too short variable names. diff --git a/wemake_python_styleguide/options/config.py b/wemake_python_styleguide/options/config.py index 6b48c6825..46a8f04ab 100644 --- a/wemake_python_styleguide/options/config.py +++ b/wemake_python_styleguide/options/config.py @@ -2,35 +2,23 @@ from typing import Dict, Sequence, Union +import attr from flake8.options.manager import OptionManager from wemake_python_styleguide.options import defaults +ConfigValues = Dict[str, Union[str, int, bool]] -class _Option(object): + +@attr.attrs(frozen=True, auto_attribs=True, slots=True) +class Option(object): """This class represent `flake8` option object.""" - def __init__( - self, - name: str, - default_value: int, - help_text: str, - option_type: type = int, - parse_from_config: bool = True, - ) -> None: - self.name = name - self.default_value = default_value - self.help_text = help_text - self.option_type = option_type - self.parse_from_config = parse_from_config - - def to_option(self) -> Dict[str, Union[str, int, type]]: - return { - 'parse_from_config': self.parse_from_config, - 'type': self.option_type, - 'default': self.default_value, - 'help': self.help_text, - } + long_option_name: str + default: int # noqa: E704 + help: str + type: str = 'int' # noqa: A003 + parse_from_config: bool = True class Configuration(object): @@ -62,75 +50,83 @@ class Configuration(object): defaults to ``MAX_ARGUMENTS`` - `min-variable-length` - minimum number of chars to define a valid variable name, defaults to ``MIN_VARIABLE_LENGTH`` - - `max_offset_blocks` - maximum number of block to nest expressions, + - `max-offset-blocks` - maximum number of block to nest expressions, defaults to ``MAX_OFFSET_BLOCKS`` - - `max_elifs` - maximum number of `elif` blocks, defaults to ``MAX_ELIFS`` - - `max_module_members` - maximum number of classes and functions + - `max-elifs` - maximum number of `elif` blocks, defaults to ``MAX_ELIFS`` + - `max-module-members` - maximum number of classes and functions in a single module, defaults to ``MAX_MODULE_MEMBERS`` - - `max_methods` - maximum number of methods in a single class, + - `max-methods` - maximum number of methods in a single class, defaults to ``MAX_METHODS`` + - `min-module-name-length` - minimum required module's name length, + defaults to ``MIN_MODULE_NAME_LENGTH`` """ - def _all_options(self) -> Sequence[_Option]: + def _all_options(self) -> Sequence[Option]: return [ - _Option( + Option( '--max-returns', defaults.MAX_RETURNS, 'Maximum allowed number of return statements in one function.', ), - _Option( + Option( '--max-local-variables', defaults.MAX_LOCAL_VARIABLES, 'Maximum allowed number of local variables in one function.', ), - _Option( + Option( '--max-expressions', defaults.MAX_EXPRESSIONS, 'Maximum allowed number of expressions in one function.', ), - _Option( + Option( '--max-arguments', defaults.MAX_ARGUMENTS, 'Maximum allowed number of arguments in one function.', ), - _Option( + Option( '--min-variable-length', defaults.MIN_VARIABLE_LENGTH, 'Minimum required length of the variable name.', ), - _Option( + Option( '--max-offset-blocks', defaults.MAX_OFFSET_BLOCKS, 'Maximum number of blocks to nest different structures.', ), - _Option( - '--max_elifs', + Option( + '--max-elifs', defaults.MAX_ELIFS, 'Maximum number of `elif` blocks.', ), - _Option( - '--max_module_members', + Option( + '--max-module-members', defaults.MAX_MODULE_MEMBERS, 'Maximum number of classes and functions in a single module.', ), - _Option( - '--max_methods', + Option( + '--max-methods', defaults.MAX_METHODS, 'Maximum number of methods in a single class.', ), + + Option( + '--min-module-name-length', + defaults.MIN_MODULE_NAME_LENGTH, + "Minimum required module's name length", + ), ] def register_options(self, parser: OptionManager) -> None: """Registers options for our plugin.""" options = self._all_options() for option in options: - parser.add_option(option.name, **option.to_option()) + parser.add_option(**attr.asdict(option)) diff --git a/wemake_python_styleguide/options/defaults.py b/wemake_python_styleguide/options/defaults.py index bc669a3d1..b077724fa 100644 --- a/wemake_python_styleguide/options/defaults.py +++ b/wemake_python_styleguide/options/defaults.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ -Constants with default values for configuration. +Constants with default values for plugin's configuration. We try to stick to "the magical 7 ± 2 number". https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two @@ -10,8 +10,8 @@ capacity. And it is really hard to keep in mind more that 9 objects at the same time. -These values can be changed in the `setup.cfg` file, if you find them -too strict or too permissive. +These values can be changed in the `setup.cfg` file on a per-project bases, +if you find them too strict or too permissive. """ #: Maximum number of `return` statements allowed in a single function: @@ -40,3 +40,9 @@ #: Maximum number of methods in a single class: MAX_METHODS = 7 + + +# Modules + +#: Minimum required module's name length: +MIN_MODULE_NAME_LENGTH = 3 diff --git a/wemake_python_styleguide/types.py b/wemake_python_styleguide/types.py index d195342cb..2421150ab 100644 --- a/wemake_python_styleguide/types.py +++ b/wemake_python_styleguide/types.py @@ -13,13 +13,13 @@ from typing_extensions import Protocol # noqa: Z101 # This solves cycle imports problem: - from .visitors.base import visitor # noqa: Z100,Z101,F401 + from .visitors import base # noqa: Z100,Z101,F401 else: # We do not need to do anything if typechecker is not working: Protocol = object -#: Visitors container, that has all enable visitors' classes: -VisitorSequence = Sequence[Type['visitor.BaseNodeVisitor']] +#: Checkers container, that has all enabled visitors' classes: +CheckerSequence = Sequence[Type['base.BaseChecker']] #: In cases we need to work with both import types: AnyImport = Union[ast.Import, ast.ImportFrom] @@ -50,3 +50,6 @@ class or structure. max_elifs: int max_module_members: int max_methods: int + + # Modules: + min_module_name_length: int diff --git a/wemake_python_styleguide/version.py b/wemake_python_styleguide/version.py index e903208d6..ec4f990bb 100644 --- a/wemake_python_styleguide/version.py +++ b/wemake_python_styleguide/version.py @@ -3,4 +3,6 @@ import pkg_resources #: We store the version number inside the `pyproject.toml`: -version = pkg_resources.get_distribution('wemake-python-styleguide').version +version: str = pkg_resources.get_distribution( + 'wemake-python-styleguide', +).version diff --git a/wemake_python_styleguide/visitors/base/__init__.py b/wemake_python_styleguide/visitors/ast/__init__.py similarity index 100% rename from wemake_python_styleguide/visitors/base/__init__.py rename to wemake_python_styleguide/visitors/ast/__init__.py diff --git a/wemake_python_styleguide/visitors/complexity/__init__.py b/wemake_python_styleguide/visitors/ast/complexity/__init__.py similarity index 100% rename from wemake_python_styleguide/visitors/complexity/__init__.py rename to wemake_python_styleguide/visitors/ast/complexity/__init__.py diff --git a/wemake_python_styleguide/visitors/complexity/counts.py b/wemake_python_styleguide/visitors/ast/complexity/counts.py similarity index 87% rename from wemake_python_styleguide/visitors/complexity/counts.py rename to wemake_python_styleguide/visitors/ast/complexity/counts.py index c72d06780..89f526fae 100644 --- a/wemake_python_styleguide/visitors/complexity/counts.py +++ b/wemake_python_styleguide/visitors/ast/complexity/counts.py @@ -11,7 +11,7 @@ from wemake_python_styleguide.logics.functions import is_method from wemake_python_styleguide.logics.limits import has_just_exceeded_limit from wemake_python_styleguide.types import ModuleMembers -from wemake_python_styleguide.visitors.base.visitor import BaseNodeVisitor +from wemake_python_styleguide.visitors.base import BaseNodeVisitor class ModuleMembersVisitor(BaseNodeVisitor): @@ -22,7 +22,7 @@ def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._public_items_count = 0 - def _check_members_count(self, node: ModuleMembers): + def _check_members_count(self, node: ModuleMembers) -> None: """This method increases the number of module members.""" parent = getattr(node, 'parent', None) is_real_method = is_method(getattr(node, 'function_type', None)) @@ -35,7 +35,7 @@ def _check_members_count(self, node: ModuleMembers): TooManyModuleMembersViolation(node, text=self.filename), ) - def visit_ClassDef(self, node: ast.ClassDef): + def visit_ClassDef(self, node: ast.ClassDef) -> None: """ Counts the number of `class`es in a single module. @@ -46,7 +46,7 @@ def visit_ClassDef(self, node: ast.ClassDef): self._check_members_count(node) self.generic_visit(node) - def visit_FunctionDef(self, node: ast.FunctionDef): + def visit_FunctionDef(self, node: ast.FunctionDef) -> None: """ Counts the number of functions in a single module. @@ -66,7 +66,7 @@ def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._methods: DefaultDict[ast.ClassDef, int] = defaultdict(int) - def _check_method(self, node: ast.FunctionDef): + def _check_method(self, node: ast.FunctionDef) -> None: parent = getattr(node, 'parent', None) if isinstance(parent, ast.ClassDef): self._methods[parent] += 1 @@ -74,7 +74,7 @@ def _check_method(self, node: ast.FunctionDef): if has_just_exceeded_limit(self._methods[parent], max_methods): self.add_error(TooManyMethodsViolation(node, text=parent.name)) - def visit_FunctionDef(self, node: ast.FunctionDef): + def visit_FunctionDef(self, node: ast.FunctionDef) -> None: """ Counts the number of methods in a single class. diff --git a/wemake_python_styleguide/visitors/complexity/function.py b/wemake_python_styleguide/visitors/ast/complexity/function.py similarity index 89% rename from wemake_python_styleguide/visitors/complexity/function.py rename to wemake_python_styleguide/visitors/ast/complexity/function.py index ce114b527..cdb247504 100644 --- a/wemake_python_styleguide/visitors/complexity/function.py +++ b/wemake_python_styleguide/visitors/ast/complexity/function.py @@ -14,7 +14,7 @@ ) from wemake_python_styleguide.logics.functions import is_method from wemake_python_styleguide.logics.limits import has_just_exceeded_limit -from wemake_python_styleguide.visitors.base.visitor import BaseNodeVisitor +from wemake_python_styleguide.visitors.base import BaseNodeVisitor class _ComplexityCounter(object): @@ -27,7 +27,11 @@ def __init__(self, delegate: 'FunctionComplexityVisitor') -> None: self.variables: DefaultDict[str, List[str]] = defaultdict(list) self.returns: DefaultDict[str, int] = defaultdict(int) - def _update_variables(self, function: ast.FunctionDef, variable_name: str): + def _update_variables( + self, + function: ast.FunctionDef, + variable_name: str, + ) -> None: """ Increases the counter of local variables. @@ -53,7 +57,7 @@ def _update_counter( counter: DefaultDict[str, int], max_value: int, exception: Type[BaseStyleViolation], - ): + ) -> None: counter[function.name] += 1 limit_exceeded = has_just_exceeded_limit( counter[function.name], max_value, @@ -61,14 +65,14 @@ def _update_counter( if limit_exceeded: self.delegate.add_error(exception(function, text=function.name)) - def _update_elifs(self, node: ast.If, count: int = 0): + def _update_elifs(self, node: ast.If, count: int = 0) -> None: if node.orelse and isinstance(node.orelse[0], ast.If): self._update_elifs(node.orelse[0], count=count + 1) else: if count > self.delegate.options.max_elifs: self.delegate.add_error(TooManyElifsViolation(node)) - def _check_sub_node(self, node: ast.FunctionDef, sub_node): + def _check_sub_node(self, node: ast.FunctionDef, sub_node) -> None: is_variable = isinstance(sub_node, ast.Name) context = getattr(sub_node, 'ctx', None) @@ -91,7 +95,7 @@ def _check_sub_node(self, node: ast.FunctionDef, sub_node): if isinstance(sub_node, ast.If): self._update_elifs(sub_node) - def check_arguments_count(self, node: ast.FunctionDef): + def check_arguments_count(self, node: ast.FunctionDef) -> None: """Checks the number of the arguments in a function.""" counter = 0 has_extra_arg = 0 @@ -111,7 +115,7 @@ def check_arguments_count(self, node: ast.FunctionDef): TooManyArgumentsViolation(node, text=node.name), ) - def check_function_complexity(self, node: ast.FunctionDef): + def check_function_complexity(self, node: ast.FunctionDef) -> None: """ In this function we iterate all the internal body's node. @@ -141,7 +145,7 @@ def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._counter = _ComplexityCounter(self) - def visit_FunctionDef(self, node: ast.FunctionDef): + def visit_FunctionDef(self, node: ast.FunctionDef) -> None: """ Checks function's internal complexity. diff --git a/wemake_python_styleguide/visitors/complexity/nested.py b/wemake_python_styleguide/visitors/ast/complexity/nested.py similarity index 89% rename from wemake_python_styleguide/visitors/complexity/nested.py rename to wemake_python_styleguide/visitors/ast/complexity/nested.py index edce4963a..a43ff5805 100644 --- a/wemake_python_styleguide/visitors/complexity/nested.py +++ b/wemake_python_styleguide/visitors/ast/complexity/nested.py @@ -10,7 +10,7 @@ NestedClassViolation, NestedFunctionViolation, ) -from wemake_python_styleguide.visitors.base.visitor import BaseNodeVisitor +from wemake_python_styleguide.visitors.base import BaseNodeVisitor class NestedComplexityVisitor(BaseNodeVisitor): @@ -23,7 +23,7 @@ class NestedComplexityVisitor(BaseNodeVisitor): We allow to nest function inside classes, that's called methods. """ - def visit_ClassDef(self, node: ast.ClassDef): + def visit_ClassDef(self, node: ast.ClassDef) -> None: """ Used to find nested classes in other classes and functions. @@ -43,7 +43,7 @@ def visit_ClassDef(self, node: ast.ClassDef): self.add_error(NestedClassViolation(node, text=node.name)) self.generic_visit(node) - def visit_FunctionDef(self, node: ast.FunctionDef): + def visit_FunctionDef(self, node: ast.FunctionDef) -> None: """ Used to find nested functions. @@ -63,7 +63,7 @@ def visit_FunctionDef(self, node: ast.FunctionDef): self.add_error(NestedFunctionViolation(node, text=node.name)) self.generic_visit(node) - def visit_Lambda(self, node: ast.Lambda): + def visit_Lambda(self, node: ast.Lambda) -> None: """ Used to find nested ``lambda``s. diff --git a/wemake_python_styleguide/visitors/complexity/offset.py b/wemake_python_styleguide/visitors/ast/complexity/offset.py similarity index 86% rename from wemake_python_styleguide/visitors/complexity/offset.py rename to wemake_python_styleguide/visitors/ast/complexity/offset.py index 4262ac6ca..fcd35fc9a 100644 --- a/wemake_python_styleguide/visitors/complexity/offset.py +++ b/wemake_python_styleguide/visitors/ast/complexity/offset.py @@ -3,18 +3,18 @@ import ast from wemake_python_styleguide.errors.complexity import TooDeepNestingViolation -from wemake_python_styleguide.visitors.base.visitor import BaseNodeVisitor +from wemake_python_styleguide.visitors.base import BaseNodeVisitor class OffsetVisitor(BaseNodeVisitor): """This visitor checks offset values for some nodes.""" - def _check_offset(self, node: ast.AST): + def _check_offset(self, node: ast.AST) -> None: offset = getattr(node, 'col_offset', None) if offset is not None and offset > self.options.max_offset_blocks * 4: self.add_error(TooDeepNestingViolation(node)) - def visit_Expr(self, node: ast.AST): + def visit_Expr(self, node: ast.AST) -> None: """ Checks statement's offset. diff --git a/wemake_python_styleguide/visitors/wrong_class.py b/wemake_python_styleguide/visitors/ast/wrong_class.py similarity index 67% rename from wemake_python_styleguide/visitors/wrong_class.py rename to wemake_python_styleguide/visitors/ast/wrong_class.py index 983d1877e..196387a22 100644 --- a/wemake_python_styleguide/visitors/wrong_class.py +++ b/wemake_python_styleguide/visitors/ast/wrong_class.py @@ -8,7 +8,7 @@ RequiredBaseClassViolation, StaticMethodViolation, ) -from wemake_python_styleguide.visitors.base.visitor import BaseNodeVisitor +from wemake_python_styleguide.visitors.base import BaseNodeVisitor class WrongClassVisitor(BaseNodeVisitor): @@ -18,22 +18,21 @@ class WrongClassVisitor(BaseNodeVisitor): Here we check for stylistic issues and design patterns. """ - def _check_decorators(self, node: ast.FunctionDef): - decorators = getattr(node, 'decorator_list', []) - for decorator in decorators: - name = getattr(decorator, 'id', None) - if name == 'staticmethod': - self.add_error(StaticMethodViolation(node, text=node.name)) + def _check_decorators(self, node: ast.FunctionDef) -> None: + for decorator in node.decorator_list: + decorator_name = getattr(decorator, 'id', None) + if decorator_name == 'staticmethod': + self.add_error(StaticMethodViolation(node)) - def _check_magic_methods(self, node: ast.FunctionDef): + def _check_magic_methods(self, node: ast.FunctionDef) -> None: if node.name in BAD_MAGIC_METHODS: self.add_error(BadMagicMethodViolation(node, text=node.name)) - def _check_base_class(self, node: ast.ClassDef): + def _check_base_class(self, node: ast.ClassDef) -> None: if len(node.bases) == 0: self.add_error(RequiredBaseClassViolation(node, text=node.name)) - def visit_ClassDef(self, node: ast.ClassDef): + def visit_ClassDef(self, node: ast.ClassDef) -> None: """ Checking class definitions. @@ -47,7 +46,7 @@ def visit_ClassDef(self, node: ast.ClassDef): self._check_base_class(node) self.generic_visit(node) - def visit_FunctionDef(self, node: ast.FunctionDef): + def visit_FunctionDef(self, node: ast.FunctionDef) -> None: """ Checking class methods. diff --git a/wemake_python_styleguide/visitors/ast/wrong_contents.py b/wemake_python_styleguide/visitors/ast/wrong_contents.py new file mode 100644 index 000000000..e5666265d --- /dev/null +++ b/wemake_python_styleguide/visitors/ast/wrong_contents.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +import ast + +from wemake_python_styleguide.constants import INIT +from wemake_python_styleguide.errors.general import ( + EmptyModuleViolation, + InitModuleHasLogicViolation, +) +from wemake_python_styleguide.logics.filenames import is_stem_in_list +from wemake_python_styleguide.visitors.base import BaseNodeVisitor + + +class WrongContentsVisitor(BaseNodeVisitor): + """Restricts to have empty modules.""" + + def _is_init(self) -> bool: + return is_stem_in_list(self.filename, [INIT]) + + def _is_doc_string(self, node: ast.stmt) -> bool: + if not isinstance(node, ast.Expr): + return False + return isinstance(node.value, ast.Str) + + def _check_module_contents(self, node: ast.Module) -> None: + if self._is_init(): + return + if not node.body: + self.add_error(EmptyModuleViolation(node)) + + def _check_init_contents(self, node: ast.Module) -> None: + if not self._is_init() or not node.body: + return + + if len(node.body) > 1: + self.add_error(InitModuleHasLogicViolation(node)) + return + + if not self._is_doc_string(node.body[0]): + self.add_error(InitModuleHasLogicViolation(node)) + + def visit_Module(self, node: ast.Module) -> None: + """ + Checks that module has something other than module definition. + + We have completely different rules for `__init__.py` and regular files. + Since, we believe that `__init__.py` must be empty. + But, other files must not be empty. + + Raises: + EmptyModuleViolation + InitModuleHasLogicViolation + + """ + self._check_init_contents(node) + self._check_module_contents(node) + self.generic_visit(node) diff --git a/wemake_python_styleguide/visitors/wrong_function_call.py b/wemake_python_styleguide/visitors/ast/wrong_function_call.py similarity index 87% rename from wemake_python_styleguide/visitors/wrong_function_call.py rename to wemake_python_styleguide/visitors/ast/wrong_function_call.py index e14b571a2..046eb83dc 100644 --- a/wemake_python_styleguide/visitors/wrong_function_call.py +++ b/wemake_python_styleguide/visitors/ast/wrong_function_call.py @@ -5,7 +5,7 @@ from wemake_python_styleguide.constants import BAD_FUNCTIONS from wemake_python_styleguide.errors.general import WrongFunctionCallViolation from wemake_python_styleguide.logics.functions import given_function_called -from wemake_python_styleguide.visitors.base.visitor import BaseNodeVisitor +from wemake_python_styleguide.visitors.base import BaseNodeVisitor class WrongFunctionCallVisitor(BaseNodeVisitor): @@ -15,7 +15,7 @@ class WrongFunctionCallVisitor(BaseNodeVisitor): All these functions are defined in `BAD_FUNCTIONS`. """ - def visit_Call(self, node: ast.Call): + def visit_Call(self, node: ast.Call) -> None: """ Used to find `BAD_FUNCTIONS` calls. diff --git a/wemake_python_styleguide/visitors/wrong_import.py b/wemake_python_styleguide/visitors/ast/wrong_import.py similarity index 83% rename from wemake_python_styleguide/visitors/wrong_import.py rename to wemake_python_styleguide/visitors/ast/wrong_import.py index 7c81e94a7..2ae2ea978 100644 --- a/wemake_python_styleguide/visitors/wrong_import.py +++ b/wemake_python_styleguide/visitors/ast/wrong_import.py @@ -12,7 +12,7 @@ ) from wemake_python_styleguide.logics.imports import get_error_text from wemake_python_styleguide.types import AnyImport -from wemake_python_styleguide.visitors.base.visitor import BaseNodeVisitor +from wemake_python_styleguide.visitors.base import BaseNodeVisitor class _ImportsChecker(object): @@ -20,20 +20,20 @@ class _ImportsChecker(object): def __init__(self, delegate: 'WrongImportVisitor') -> None: self.delegate = delegate - def check_nested_import(self, node: AnyImport): + def check_nested_import(self, node: AnyImport) -> None: text = get_error_text(node) parent = getattr(node, 'parent', None) if not isinstance(parent, ast.Module): self.delegate.add_error(NestedImportViolation(node, text=text)) - def check_local_import(self, node: ast.ImportFrom): + def check_local_import(self, node: ast.ImportFrom) -> None: text = get_error_text(node) if node.level != 0: self.delegate.add_error( LocalFolderImportViolation(node, text=text), ) - def check_future_import(self, node: ast.ImportFrom): + def check_future_import(self, node: ast.ImportFrom) -> None: if node.module == '__future__': for alias in node.names: if alias.name not in FUTURE_IMPORTS_WHITELIST: @@ -41,14 +41,14 @@ def check_future_import(self, node: ast.ImportFrom): FutureImportViolation(node, text=alias.name), ) - def check_dotted_raw_import(self, node: ast.Import): + def check_dotted_raw_import(self, node: ast.Import) -> None: for alias in node.names: if '.' in alias.name: self.delegate.add_error( DottedRawImportViolation(node, text=alias.name), ) - def check_alias(self, node: AnyImport): + def check_alias(self, node: AnyImport) -> None: for alias in node.names: if alias.asname == alias.name: self.delegate.add_error( @@ -60,11 +60,11 @@ class WrongImportVisitor(BaseNodeVisitor): """This class is responsible for finding wrong imports.""" def __init__(self, *args, **kwargs) -> None: - """Creates a checher for tracked violations.""" + """Creates a checker for tracked violations.""" super().__init__(*args, **kwargs) self._checker = _ImportsChecker(self) - def visit_Import(self, node: ast.Import): + def visit_Import(self, node: ast.Import) -> None: """ Used to find wrong `import` statements. @@ -79,7 +79,7 @@ def visit_Import(self, node: ast.Import): self._checker.check_alias(node) self.generic_visit(node) - def visit_ImportFrom(self, node: ast.ImportFrom): + def visit_ImportFrom(self, node: ast.ImportFrom) -> None: """ Used to find wrong `from import` statements. diff --git a/wemake_python_styleguide/visitors/wrong_keyword.py b/wemake_python_styleguide/visitors/ast/wrong_keyword.py similarity index 88% rename from wemake_python_styleguide/visitors/wrong_keyword.py rename to wemake_python_styleguide/visitors/ast/wrong_keyword.py index cd306ec45..73df76162 100644 --- a/wemake_python_styleguide/visitors/wrong_keyword.py +++ b/wemake_python_styleguide/visitors/ast/wrong_keyword.py @@ -7,7 +7,7 @@ RaiseNotImplementedViolation, WrongKeywordViolation, ) -from wemake_python_styleguide.visitors.base.visitor import BaseNodeVisitor +from wemake_python_styleguide.visitors.base import BaseNodeVisitor class WrongRaiseVisitor(BaseNodeVisitor): @@ -50,7 +50,7 @@ def visit_Raise(self, node: ast.Raise) -> None: class WrongKeywordVisitor(BaseNodeVisitor): """This class is responsible for finding wrong keywords.""" - def visit_Global(self, node: ast.Global): + def visit_Global(self, node: ast.Global) -> None: """ Used to find `global` keyword. @@ -61,7 +61,7 @@ def visit_Global(self, node: ast.Global): self.add_error(WrongKeywordViolation(node)) self.generic_visit(node) - def visit_Nonlocal(self, node: ast.Nonlocal): + def visit_Nonlocal(self, node: ast.Nonlocal) -> None: """ Used to find `nonlocal` keyword. @@ -72,7 +72,7 @@ def visit_Nonlocal(self, node: ast.Nonlocal): self.add_error(WrongKeywordViolation(node)) self.generic_visit(node) - def visit_Delete(self, node: ast.Delete): + def visit_Delete(self, node: ast.Delete) -> None: """ Used to find `del` keyword. @@ -83,7 +83,7 @@ def visit_Delete(self, node: ast.Delete): self.add_error(WrongKeywordViolation(node, text='del')) self.generic_visit(node) - def visit_Pass(self, node: ast.Pass): + def visit_Pass(self, node: ast.Pass) -> None: """ Used to find `pass` keyword. diff --git a/wemake_python_styleguide/visitors/wrong_name.py b/wemake_python_styleguide/visitors/ast/wrong_name.py similarity index 77% rename from wemake_python_styleguide/visitors/wrong_name.py rename to wemake_python_styleguide/visitors/ast/wrong_name.py index e201d8aa3..f5a2e8b15 100644 --- a/wemake_python_styleguide/visitors/wrong_name.py +++ b/wemake_python_styleguide/visitors/ast/wrong_name.py @@ -18,7 +18,7 @@ is_wrong_variable_name, ) from wemake_python_styleguide.types import AnyImport -from wemake_python_styleguide.visitors.base.visitor import BaseNodeVisitor +from wemake_python_styleguide.visitors.base import BaseNodeVisitor class WrongNameVisitor(BaseNodeVisitor): @@ -29,16 +29,16 @@ class WrongNameVisitor(BaseNodeVisitor): functions, and arguments. """ - def _check_name(self, node: ast.AST, arg: str) -> None: - if is_wrong_variable_name(arg, BAD_VARIABLE_NAMES): - self.add_error(WrongVariableNameViolation(node, text=arg)) + def _check_name(self, node: ast.AST, name: str) -> None: + if is_wrong_variable_name(name, BAD_VARIABLE_NAMES): + self.add_error(WrongVariableNameViolation(node, text=name)) min_length = self.options.min_variable_length - if is_too_short_variable_name(arg, min_length=min_length): - self.add_error(TooShortVariableNameViolation(node, text=arg)) + if is_too_short_variable_name(name, min_length=min_length): + self.add_error(TooShortVariableNameViolation(node, text=name)) - if is_private_variable(arg): - self.add_error(PrivateNameViolation(node, text=arg)) + if is_private_variable(name): + self.add_error(PrivateNameViolation(node, text=name)) def _check_function_signature(self, node: ast.FunctionDef) -> None: for arg in node.args.args: @@ -53,7 +53,7 @@ def _check_function_signature(self, node: ast.FunctionDef) -> None: if node.args.kwarg: self._check_name(node, node.args.kwarg.arg) - def visit_Attribute(self, node: ast.Attribute): + def visit_Attribute(self, node: ast.Attribute) -> None: """ Used to find wrong attribute names inside classes. @@ -63,14 +63,12 @@ def visit_Attribute(self, node: ast.Attribute): PrivateNameViolation """ - context = getattr(node, 'ctx', None) - - if isinstance(context, ast.Store): + if isinstance(node.ctx, ast.Store): self._check_name(node, node.attr) self.generic_visit(node) - def visit_FunctionDef(self, node: ast.FunctionDef): + def visit_FunctionDef(self, node: ast.FunctionDef) -> None: """ Used to find wrong function and method parameters. @@ -80,12 +78,11 @@ def visit_FunctionDef(self, node: ast.FunctionDef): PrivateNameViolation """ - name = getattr(node, 'name', None) - self._check_name(node, name) + self._check_name(node, node.name) self._check_function_signature(node) self.generic_visit(node) - def visit_ExceptHandler(self, node: ast.ExceptHandler): + def visit_ExceptHandler(self, node: ast.ExceptHandler) -> None: """ Used to find wrong exception instances in ``try``/``except``. @@ -95,11 +92,10 @@ def visit_ExceptHandler(self, node: ast.ExceptHandler): PrivateNameViolation """ - name = getattr(node, 'name', None) - self._check_name(node, name) + self._check_name(node, getattr(node, 'name', None)) self.generic_visit(node) - def visit_Name(self, node: ast.Name): + def visit_Name(self, node: ast.Name) -> None: """ Used to find wrong regular variables. @@ -109,13 +105,12 @@ def visit_Name(self, node: ast.Name): PrivateNameViolation """ - context = getattr(node, 'ctx', None) - if isinstance(context, ast.Store): + if isinstance(node.ctx, ast.Store): self._check_name(node, node.id) self.generic_visit(node) - def visit_Import(self, node: AnyImport): + def visit_Import(self, node: AnyImport) -> None: """ Used to check wrong import alias names. @@ -137,7 +132,7 @@ def visit_Import(self, node: AnyImport): class WrongModuleMetadataVisitor(BaseNodeVisitor): """This class finds wrong metadata information of a module.""" - def _check_metadata(self, node: ast.Assign): + def _check_metadata(self, node: ast.Assign) -> None: node_parent = getattr(node, 'parent', None) if not isinstance(node_parent, ast.Module): return @@ -149,7 +144,7 @@ def _check_metadata(self, node: ast.Assign): WrongModuleMetadataViolation(node, text=target_node_id), ) - def visit_Assign(self, node: ast.Assign): + def visit_Assign(self, node: ast.Assign) -> None: """ Used to find the bad metadata variable names. diff --git a/wemake_python_styleguide/visitors/wrong_string.py b/wemake_python_styleguide/visitors/ast/wrong_string.py similarity index 72% rename from wemake_python_styleguide/visitors/wrong_string.py rename to wemake_python_styleguide/visitors/ast/wrong_string.py index 531a7781d..676c44770 100644 --- a/wemake_python_styleguide/visitors/wrong_string.py +++ b/wemake_python_styleguide/visitors/ast/wrong_string.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- from wemake_python_styleguide.errors.general import FormattedStringViolation -from wemake_python_styleguide.visitors.base.visitor import BaseNodeVisitor +from wemake_python_styleguide.visitors.base import BaseNodeVisitor class WrongStringVisitor(BaseNodeVisitor): """Restricts to use `f` strings.""" - def visit_JoinedStr(self, node): # type is not defined in `ast` yet + def visit_JoinedStr(self, node) -> None: # type is not defined in ast yet """ Restricts to use `f` strings. diff --git a/wemake_python_styleguide/visitors/base.py b/wemake_python_styleguide/visitors/base.py new file mode 100644 index 000000000..7078728d2 --- /dev/null +++ b/wemake_python_styleguide/visitors/base.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +import ast +from typing import List + +from wemake_python_styleguide import constants +from wemake_python_styleguide.errors.base import BaseStyleViolation +from wemake_python_styleguide.types import ConfigurationOptions + + +class BaseChecker(object): + """ + Base class for different type of checkers. + + Attributes: + tree: AST tree to be checked if any. + options: contains the options objects passed and parsed by `flake8`. + filename: filename passed by `flake8`. + errors: list of errors for the specific checker. + + """ + + def __init__( + self, + options: ConfigurationOptions, + tree: ast.AST = None, + filename: str = 'stdin', + ) -> None: + """Creates new instance.""" + super().__init__() + self.options = options + self.tree = tree + self.filename = filename + self.errors: List[BaseStyleViolation] = [] + + def add_error(self, error: BaseStyleViolation) -> None: + """Adds error to the visitor.""" + self.errors.append(error) + + def run(self) -> None: + """This method should be defined in all subclasses of this class.""" + raise NotImplementedError('Should be defined in a subclass') + + +class BaseNodeVisitor(ast.NodeVisitor, BaseChecker): + """ + This class allows to store errors while traversing node tree. + + This class should be used as a base class for all `ast`-based checkers. + Method `visit()` is defined in `NodeVisitor` class. + """ + + def run(self) -> None: + """Runs `visit()` method of `NodeVisitor` with the correct params.""" + if self.tree is None: + raise ValueError('Parsing without a defined trie') + self.visit(self.tree) + + +class BaseFilenameVisitor(BaseChecker): + """ + This class allows to check module filenames. + + Method `visit()` is used only for API compatibility. + """ + + def visit_filename(self) -> None: + """This method should be overridden in a subclass.""" + raise NotImplementedError('Should be defined in a subclass') + + def run(self) -> None: + """ + Checks module's filename. + + If filename equals to ``STDIN`` constant then this check is ignored. + Otherwise, runs `visit_filename()` method. + """ + if self.filename != constants.STDIN: + self.visit_filename() diff --git a/wemake_python_styleguide/visitors/base/visitor.py b/wemake_python_styleguide/visitors/base/visitor.py deleted file mode 100644 index f753e9e62..000000000 --- a/wemake_python_styleguide/visitors/base/visitor.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- - -from ast import NodeVisitor -from typing import List - -from wemake_python_styleguide.errors.base import BaseStyleViolation -from wemake_python_styleguide.types import ConfigurationOptions - - -class BaseNodeVisitor(NodeVisitor): - """ - This class allows to store errors while traversing node tree. - - Attributes: - options: contains the options objects passed and parsed by `flake8`. - filename: filename passed by `flake8`. - errors: list of errors for the specific checker. - - """ - - def __init__( - self, options: ConfigurationOptions, filename: str = 'stdin', - ) -> None: - """Creates new visitor instance.""" - super().__init__() - self.options = options - self.filename = filename - self.errors: List[BaseStyleViolation] = [] - - def add_error(self, error: BaseStyleViolation) -> None: - """Adds error to the visitor.""" - self.errors.append(error) diff --git a/wemake_python_styleguide/visitors/filenames/__init__.py b/wemake_python_styleguide/visitors/filenames/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/wemake_python_styleguide/visitors/filenames/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/wemake_python_styleguide/visitors/filenames/wrong_module_name.py b/wemake_python_styleguide/visitors/filenames/wrong_module_name.py new file mode 100644 index 000000000..d815ca887 --- /dev/null +++ b/wemake_python_styleguide/visitors/filenames/wrong_module_name.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +from wemake_python_styleguide.constants import ( + BAD_MODULE_NAMES, + MAGIC_MODULE_NAMES_WHITELIST, +) +from wemake_python_styleguide.errors.modules import ( + TooShortModuleNameViolation, + WrongModuleMagicNameViolation, + WrongModuleNameViolation, +) +from wemake_python_styleguide.logics.filenames import ( + is_magic, + is_stem_in_list, + is_too_short_stem, +) +from wemake_python_styleguide.visitors.base import BaseFilenameVisitor + + +class WrongModuleNameVisitor(BaseFilenameVisitor): + """Checks that modules have correct names.""" + + def _check_module_name(self) -> None: + if is_stem_in_list(self.filename, BAD_MODULE_NAMES): + self.add_error(WrongModuleNameViolation()) + + def _check_magic_name(self) -> None: + if is_magic(self.filename): + good_magic = is_stem_in_list( + self.filename, + MAGIC_MODULE_NAMES_WHITELIST, + ) + if not good_magic: + self.add_error(WrongModuleMagicNameViolation()) + + def _check_module_name_length(self) -> None: + is_short = is_too_short_stem( + self.filename, + min_length=self.options.min_module_name_length, + ) + if is_short: + self.add_error(TooShortModuleNameViolation()) + + def visit_filename(self) -> None: + """ + Checks a single module's filename. + + Raises: + TooShortModuleNameViolation + WrongModuleMagicNameViolation + WrongModuleNameViolation + + """ + self._check_module_name() + self._check_magic_name() + self._check_module_name_length()