From 889080f36448983aa6c9ba0d2456996e4a1ff8c2 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 10 Nov 2018 15:12:00 +0300 Subject: [PATCH] Version 0.5.0 release, closes #312 --- CHANGELOG.md | 10 +++-- pyproject.toml | 2 +- tests/test_checker/test_module_names.py | 7 +++- .../test_counts/test_method_counts.py | 8 ++-- .../test_counts/test_module_counts.py | 12 +++--- .../test_comprehensions/test_for_count.py | 6 +-- .../test_naming/test_length_config.py | 27 -------------- .../test_naming_rules/test_short.py | 24 ++++++++++++ .../test_keywords_spaces.py | 2 +- .../logics/naming/logical.py | 37 ++++++++++++++++--- wemake_python_styleguide/options/defaults.py | 2 +- wemake_python_styleguide/violations/naming.py | 5 ++- .../visitors/ast/naming.py | 9 ++--- .../visitors/filenames/module.py | 4 ++ 14 files changed, 93 insertions(+), 62 deletions(-) delete mode 100644 tests/test_visitors/test_ast/test_naming/test_length_config.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 539a0a166..b5b5e8348 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,22 +3,24 @@ We follow Semantic Versions since the `0.1.0` release. We used to have incremental versioning before `0.1.0`. -## WIP +## 0.5.0 ### Features -- Adds `TooLongNameViolation` - **Breaking**: removes `--max-conditions` and `--max-elifs` options - **Breaking**: removes `--max-offset-blocks` - **Breaking**: changes default `TooManyConditionsViolation` threshold from `3` to `4` - **Breaking**: changes `TooManyBaseClassesViolation` code from ``225`` to ``215`` - Forbids to use `lambda` inside loops -- Reserving names `self`, `cls`, and `mcs` for first arguments only +- Forbids to use `self`, `cls`, and `mcs` except for first arguments only - Forbids to use too many decorators -- Now `RedundantLoopElseViolation` also checks `while` loops - Forbids to have unreachable code - Forbids to have statements that have no effect +- Forbids to have too long names for modules and variables +- Forbids to have names with unicode for modules and variables - Add `variable` to the blacklisted names +- Now `RedundantLoopElseViolation` also checks `while` loops + ## Bugfixes diff --git a/pyproject.toml b/pyproject.toml index 52695f1bc..840de460a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wemake-python-styleguide" -version = "0.4.0" +version = "0.5.0" description = "The strictest and most opinionated python linter ever" license = "MIT" diff --git a/tests/test_checker/test_module_names.py b/tests/test_checker/test_module_names.py index eed9d73a7..f7e32a5ff 100644 --- a/tests/test_checker/test_module_names.py +++ b/tests/test_checker/test_module_names.py @@ -16,8 +16,11 @@ ('123py.py', naming.WrongModuleNamePatternViolation), ('version_1.py', naming.UnderscoredNumberNameViolation), ('__private.py', naming.PrivateNameViolation), - ('oh_no_not_an_extremely_super_duper_unreasonably_long_name.py', - naming.TooLongNameViolation), + ( + 'oh_no_not_an_extremely_super_duper_unreasonably_long_name.py', + naming.TooLongNameViolation, + ), + ('привет', naming.UnicodeNameViolation), ]) def test_module_names(filename, error, default_options): """Ensures that checker works with module names.""" diff --git a/tests/test_visitors/test_ast/test_complexity/test_counts/test_method_counts.py b/tests/test_visitors/test_ast/test_complexity/test_counts/test_method_counts.py index d00c6041c..9a964464f 100644 --- a/tests/test_visitors/test_ast/test_complexity/test_counts/test_method_counts.py +++ b/tests/test_visitors/test_ast/test_complexity/test_counts/test_method_counts.py @@ -13,13 +13,13 @@ def first(): ... def second(): ... """ -module_without_methods_with_async_functions = """ +module_with_async_functions = """ async def first(): ... async def second(): ... """ -module_without_methods_with_async_and_usual_functions = """ +module_async_and_usual_functions = """ def first(): ... async def second(): ... @@ -76,8 +76,8 @@ async def method2(cls): ... @pytest.mark.parametrize('code', [ module_without_methods, - module_without_methods_with_async_functions, - module_without_methods_with_async_and_usual_functions, + module_with_async_functions, + module_async_and_usual_functions, class_with_methods, class_with_async_methods, class_with_async_and_usual_methods, diff --git a/tests/test_visitors/test_ast/test_complexity/test_counts/test_module_counts.py b/tests/test_visitors/test_ast/test_complexity/test_counts/test_module_counts.py index 871a0ab79..8a9de51ed 100644 --- a/tests/test_visitors/test_ast/test_complexity/test_counts/test_module_counts.py +++ b/tests/test_visitors/test_ast/test_complexity/test_counts/test_module_counts.py @@ -22,14 +22,14 @@ class Second(object): def method(self): ... """ -module_with_function_and_class_and_async_method = """ +module_with_function_and_async_method = """ def first(): ... class Second(object): async def method(self): ... """ -module_with_function_and_class_and_classmethod = """ +module_with_function_and_classmethod = """ def first(): ... class Second(object): @@ -98,8 +98,8 @@ def other(self): ... empty_module, module_with_function_and_class, module_with_function_and_class_and_method, - module_with_function_and_class_and_async_method, - module_with_function_and_class_and_classmethod, + module_with_function_and_async_method, + module_with_function_and_classmethod, module_with_async_function_and_class, module_with_methods, module_with_async_methods, @@ -124,8 +124,8 @@ def test_module_counts_normal( @pytest.mark.parametrize('code', [ module_with_function_and_class, module_with_function_and_class_and_method, - module_with_function_and_class_and_async_method, - module_with_function_and_class_and_classmethod, + module_with_function_and_async_method, + module_with_function_and_classmethod, module_with_async_function_and_class, module_with_methods, module_with_async_methods, diff --git a/tests/test_visitors/test_ast/test_keywords/test_comprehensions/test_for_count.py b/tests/test_visitors/test_ast/test_keywords/test_comprehensions/test_for_count.py index 86dc7c007..3ad1eb398 100644 --- a/tests/test_visitors/test_ast/test_keywords/test_comprehensions/test_for_count.py +++ b/tests/test_visitors/test_ast/test_keywords/test_comprehensions/test_for_count.py @@ -100,7 +100,7 @@ def test_multiple_for_keywords_in_comprehension( @pytest.mark.parametrize('code', [ complex_nested_list_comprehension, ]) -def test_multiple_for_keywords_in_async_comprehension( +def test_multiple_fors_in_async_comprehension( assert_errors, parse_ast_tree, code, @@ -120,7 +120,7 @@ def test_multiple_for_keywords_in_async_comprehension( regular_dict_comprehension, regular_gen_expression, ]) -def test_regular_for_keywords_in_comprehension( +def test_regular_fors_in_comprehension( assert_errors, parse_ast_tree, code, @@ -137,7 +137,7 @@ def test_regular_for_keywords_in_comprehension( @pytest.mark.parametrize('code', [ regular_nested_list_comprehension, ]) -def test_regular_for_keywords_in_async_comprehension( +def test_regular_fors_in_async_comprehension( assert_errors, parse_ast_tree, code, diff --git a/tests/test_visitors/test_ast/test_naming/test_length_config.py b/tests/test_visitors/test_ast/test_naming/test_length_config.py deleted file mode 100644 index 0f0effb3a..000000000 --- a/tests/test_visitors/test_ast/test_naming/test_length_config.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- - -import pytest - -from wemake_python_styleguide.visitors.ast.naming import WrongNameVisitor - - -@pytest.mark.parametrize('correct_name', [ - 'snake_case', - '_protected_or_unused', - 'with_number5', -]) -def test_naming_correct( - assert_errors, - parse_ast_tree, - naming_template, - options, - correct_name, -): - """Ensures that correct names are allowed.""" - tree = parse_ast_tree(naming_template.format(correct_name)) - - option_values = options(min_name_length=3) - visitor = WrongNameVisitor(option_values, tree=tree) - visitor.run() - - assert_errors(visitor, []) diff --git a/tests/test_visitors/test_ast/test_naming/test_naming_rules/test_short.py b/tests/test_visitors/test_ast/test_naming/test_naming_rules/test_short.py index e850021e4..254041339 100644 --- a/tests/test_visitors/test_ast/test_naming/test_naming_rules/test_short.py +++ b/tests/test_visitors/test_ast/test_naming/test_naming_rules/test_short.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +import pytest + from wemake_python_styleguide.violations.naming import TooShortNameViolation from wemake_python_styleguide.visitors.ast.naming import WrongNameVisitor @@ -21,3 +23,25 @@ def test_short_variable_name( assert_errors(visitor, [TooShortNameViolation]) assert_error_text(visitor, short_name) + + +@pytest.mark.parametrize('correct_name', [ + 'snake_case', + '_protected_or_unused', + 'with_number5', +]) +def test_naming_correct( + assert_errors, + parse_ast_tree, + naming_template, + options, + correct_name, +): + """Ensures that correct names are allowed.""" + tree = parse_ast_tree(naming_template.format(correct_name)) + + option_values = options(min_name_length=3) + visitor = WrongNameVisitor(option_values, tree=tree) + visitor.run() + + assert_errors(visitor, []) diff --git a/tests/test_visitors/test_tokenize/test_wrong_keywords/test_keywords_spaces.py b/tests/test_visitors/test_tokenize/test_wrong_keywords/test_keywords_spaces.py index 0d4a19ade..b4190d806 100644 --- a/tests/test_visitors/test_tokenize/test_wrong_keywords/test_keywords_spaces.py +++ b/tests/test_visitors/test_tokenize/test_wrong_keywords/test_keywords_spaces.py @@ -92,7 +92,7 @@ def test_missing_space( multiline_correct_function, multiline_correct_statement, ]) -def test_fine_when_space_in_between_keyword_and_parens( +def test_space_between_keyword_and_parens( parse_tokens, assert_errors, default_options, diff --git a/wemake_python_styleguide/logics/naming/logical.py b/wemake_python_styleguide/logics/naming/logical.py index 92d937467..b41ca1b78 100644 --- a/wemake_python_styleguide/logics/naming/logical.py +++ b/wemake_python_styleguide/logics/naming/logical.py @@ -30,11 +30,11 @@ def is_wrong_name(name: str, to_check: Iterable[str]) -> bool: """ for name_to_check in to_check: - choices_to_check = [ + choices_to_check = { name_to_check, '_{0}'.format(name_to_check), '{0}_'.format(name_to_check), - ] + } if name in choices_to_check: return True return False @@ -74,7 +74,7 @@ def is_too_short_name( min_length: int = defaults.MIN_NAME_LENGTH, ) -> bool: """ - Checks for too short variable names. + Checks for too short names. >>> is_too_short_name('test') False @@ -100,7 +100,7 @@ def is_too_long_name( max_length: int = defaults.MAX_NAME_LENGTH, ) -> bool: """ - Checks for too long variable names. + Checks for too long names. >>> is_too_long_name('test') False @@ -123,7 +123,7 @@ def is_too_long_name( def does_contain_underscored_number(name: str) -> bool: """ - Checks for variable names with underscored number. + Checks for names with underscored number. >>> does_contain_underscored_number('star_wars_episode2') False @@ -158,7 +158,7 @@ def does_contain_underscored_number(name: str) -> bool: def does_contain_consecutive_underscores(name: str) -> bool: """ - Checks if variable contains consecutive underscores in middle of name. + Checks if name contains consecutive underscores in middle of name. >>> does_contain_consecutive_underscores('name') False @@ -186,3 +186,28 @@ def does_contain_consecutive_underscores(name: str) -> bool: return True return False + + +def does_contain_unicode(name: str) -> bool: + """ + Check if name contains unicode characters. + + >>> does_contain_unicode('hello_world1') + False + + >>> does_contain_unicode('') + False + + >>> does_contain_unicode('привет_мир1') + True + + >>> does_contain_unicode('russian_техт') + True + + """ + try: + name.encode('ascii') + except UnicodeEncodeError: + return True + else: + return False diff --git a/wemake_python_styleguide/options/defaults.py b/wemake_python_styleguide/options/defaults.py index 90fa16401..5d332914c 100644 --- a/wemake_python_styleguide/options/defaults.py +++ b/wemake_python_styleguide/options/defaults.py @@ -22,7 +22,7 @@ MIN_NAME_LENGTH: Final = 2 #: Maximum variable and module name length: -MAX_NAME_LENGTH: Final = 55 +MAX_NAME_LENGTH: Final = 45 #: Whether you control ones who use your code. I_CONTROL_CODE: Final = True diff --git a/wemake_python_styleguide/violations/naming.py b/wemake_python_styleguide/violations/naming.py index 07111d0c0..c8ff50f0f 100644 --- a/wemake_python_styleguide/violations/naming.py +++ b/wemake_python_styleguide/violations/naming.py @@ -561,7 +561,7 @@ class TooLongNameViolation(MaybeASTViolation): @final -class UnicodeNameViolation(ASTViolation): +class UnicodeNameViolation(MaybeASTViolation): """ Restrict unicode names. @@ -571,6 +571,9 @@ class UnicodeNameViolation(ASTViolation): Solution: Rename your entities so that they contain only ASCII symbols. + This rule checks: modules, variables, attributes, + functions, methods, and classes. + Example:: # Correct: diff --git a/wemake_python_styleguide/visitors/ast/naming.py b/wemake_python_styleguide/visitors/ast/naming.py index 776298bfe..7829a901e 100644 --- a/wemake_python_styleguide/visitors/ast/naming.py +++ b/wemake_python_styleguide/visitors/ast/naming.py @@ -85,12 +85,9 @@ def check_name( self._error_callback( naming.ReservedArgumentNameViolation(node, text=name), ) - try: - name.encode('ascii') - except UnicodeEncodeError: - self._error_callback( - naming.UnicodeNameViolation(node, text=name), - ) + + if logical.does_contain_unicode(name): + self._error_callback(naming.UnicodeNameViolation(node, text=name)) self._ensure_length(node, name) self._ensure_underscores(node, name) diff --git a/wemake_python_styleguide/visitors/filenames/module.py b/wemake_python_styleguide/visitors/filenames/module.py index 45b79246d..00eb38e49 100644 --- a/wemake_python_styleguide/visitors/filenames/module.py +++ b/wemake_python_styleguide/visitors/filenames/module.py @@ -9,6 +9,7 @@ TooLongNameViolation, TooShortNameViolation, UnderscoredNumberNameViolation, + UnicodeNameViolation, WrongModuleMagicNameViolation, WrongModuleNamePatternViolation, WrongModuleNameViolation, @@ -31,6 +32,9 @@ def _check_module_name(self) -> None: if access.is_private(self.stem): self.add_violation(PrivateNameViolation(text=self.stem)) + if logical.does_contain_unicode(self.stem): + self.add_violation(UnicodeNameViolation(text=self.stem)) + def _check_module_name_length(self) -> None: min_length = self.options.min_name_length if logical.is_too_short_name(self.stem, min_length=min_length):