Skip to content

Commit 46a7fc8

Browse files
committed
Restricts to use protected attributes, closes #272
1 parent 3da9bf1 commit 46a7fc8

File tree

23 files changed

+488
-172
lines changed

23 files changed

+488
-172
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ to the project during `#hactoberfest`. List of awesome people:
3838
- Forbids variable names with more than one consecutive underscore
3939
- Restricts the maximum number of base classes aka mixins
4040
- Forbids importing protected names
41+
- Forbids using protected methods and attributes
4142
- Forbids `yield` inside `__init__` method
4243

4344
### Bugfixes

tests/fixtures/noqa.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from .version import get_version # noqa: Z300
1010
import sys as sys # noqa: Z113
11+
from some import _protected # noqa: Z440
1112

1213
full_name = u'Nikita Sobolev' # noqa: Z302
1314
phone_number = 555_123_999 # noqa: Z303
@@ -29,9 +30,11 @@ def nested(): ... # noqa: Z430
2930
value = 1 # noqa: Z110
3031
x = 2 # noqa: Z111
3132
__private = 3 # noqa: Z112
33+
consecutive__underscores = 4 # noqa: Z116
3234
__author__ = 'Nikita Sobolev' # noqa: Z410
3335

3436
nodes = [node for node in 'abc' if node != 'a' if node != 'b'] # noqa: Z307
37+
some._execute() # noqa: Z441
3538

3639

3740
class BadClass: # noqa: Z306
@@ -63,7 +66,7 @@ class Nested: # noqa: Z306,Z431
6366
assert hex_number == hex_number # noqa: Z312
6467

6568
for symbol in 'abc': # noqa: Z436
66-
break
69+
...
6770
else:
6871
...
6972

tests/test_checker/test_noqa.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def test_noqa_fixture_disabled(absolute_path):
2525
'Z111': 1,
2626
'Z112': 1,
2727
'Z113': 1,
28+
'Z116': 1,
2829

2930
'Z220': 1,
3031
'Z224': 1,
@@ -54,6 +55,8 @@ def test_noqa_fixture_disabled(absolute_path):
5455
'Z435': 1,
5556
'Z436': 1,
5657
'Z437': 1,
58+
'Z440': 1,
59+
'Z441': 1,
5760
}
5861

5962
process = subprocess.Popen(

tests/test_violations/test_implementation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ def test_visitor_returns_location():
1515

1616
def test_checker_default_location():
1717
"""Ensures that `BaseViolation` returns correct location."""
18-
assert BaseViolation(None)._location() == (0, 0)
18+
assert BaseViolation(None)._location() == (0, 0) # noqa: Z441
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pytest
4+
5+
from wemake_python_styleguide.visitors.ast.attributes import (
6+
ProtectedAttributeViolation,
7+
WrongAttributeVisitor,
8+
)
9+
10+
# Incorrect:
11+
12+
protected_attribute_assigned = 'some._protected = 1'
13+
protected_attribute_accessed = 'print(some._protected)'
14+
protected_method_called = 'some._method()'
15+
protected_method_called_params = 'some._method(12, 33)'
16+
17+
protected_container_attribute = """
18+
class Test(object):
19+
def __init__(self):
20+
self.container._print = 1
21+
"""
22+
23+
protected_container_method = """
24+
class Test(object):
25+
def __init__(self):
26+
self.container._print()
27+
"""
28+
29+
# Correct:
30+
31+
protected_name_definition = '_protected = 1'
32+
protected_name_attr_definition = '_protected.some = 1'
33+
34+
protected_self_attribute = """
35+
class Test(object):
36+
def __init__(self):
37+
self._print = 1
38+
"""
39+
40+
protected_self_method = """
41+
class Test(object):
42+
def __init__(self):
43+
self._print()
44+
"""
45+
46+
protected_cls_attribute = """
47+
class Test(object):
48+
@classmethod
49+
def method(cls):
50+
cls._print = 'some'
51+
"""
52+
53+
protected_cls_method = """
54+
class Test(object):
55+
@classmethod
56+
def method(cls):
57+
cls._print()
58+
"""
59+
60+
protected_attribute_definition = """
61+
class Test(object):
62+
_protected = 1
63+
"""
64+
65+
66+
@pytest.mark.parametrize('code', [
67+
protected_attribute_assigned,
68+
protected_attribute_accessed,
69+
protected_method_called,
70+
protected_method_called_params,
71+
protected_container_attribute,
72+
protected_container_method,
73+
])
74+
def test_protected_attribute_is_restricted(
75+
assert_errors,
76+
parse_ast_tree,
77+
code,
78+
default_options,
79+
):
80+
"""Ensures that it is impossible to use protected attributes."""
81+
tree = parse_ast_tree(code)
82+
83+
visitor = WrongAttributeVisitor(default_options, tree=tree)
84+
visitor.run()
85+
86+
assert_errors(visitor, [ProtectedAttributeViolation])
87+
88+
89+
@pytest.mark.parametrize('code', [
90+
protected_name_definition,
91+
protected_name_attr_definition,
92+
protected_self_attribute,
93+
protected_self_method,
94+
protected_cls_attribute,
95+
protected_cls_method,
96+
protected_attribute_definition,
97+
])
98+
def test_protected_attribute_is_allowed(
99+
assert_errors,
100+
parse_ast_tree,
101+
code,
102+
default_options,
103+
):
104+
"""Ensures that it is possible to use protected attributes."""
105+
tree = parse_ast_tree(code)
106+
107+
visitor = WrongAttributeVisitor(default_options, tree=tree)
108+
visitor.run()
109+
110+
assert_errors(visitor, [])

tests/test_visitors/test_ast/test_strings/test_formatted_string.py renamed to tests/test_visitors/test_ast/test_builtins/test_formatted_string.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
from wemake_python_styleguide.violations.consistency import (
66
FormattedStringViolation,
77
)
8-
from wemake_python_styleguide.visitors.ast.strings import WrongStringVisitor
8+
from wemake_python_styleguide.visitors.ast.builtins import WrongStringVisitor
99

1010
regular_string = "'some value'"
11+
binary_string = "b'binary'"
12+
unicode_string = "u'unicode'"
1113
string_variable = "some = '123'"
1214
formated_string = "'x + y = {0}'.format(2)"
15+
procent_format = "'x = %d' % 1"
1316
key_formated_string = "'x + y = {res}'.format(res=2)"
1417
variable_format = """
1518
some = 'x = {0}'
@@ -22,8 +25,11 @@
2225

2326
@pytest.mark.parametrize('code', [
2427
regular_string,
28+
binary_string,
29+
unicode_string,
2530
string_variable,
2631
formated_string,
32+
procent_format,
2733
key_formated_string,
2834
variable_format,
2935
])

tests/test_visitors/test_ast/test_numbers/test_magic_numbers.py renamed to tests/test_visitors/test_ast/test_builtins/test_magic_numbers.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22

33
import pytest
44

5+
from wemake_python_styleguide.constants import MAGIC_NUMBERS_WHITELIST
56
from wemake_python_styleguide.violations.best_practices import (
67
MagicNumberViolation,
78
)
8-
from wemake_python_styleguide.visitors.ast.numbers import (
9-
MAGIC_NUMBERS_WHITELIST,
10-
MagicNumberVisitor,
11-
)
9+
from wemake_python_styleguide.visitors.ast.builtins import MagicNumberVisitor
1210

1311
# Correct usages:
1412

tests/test_visitors/test_ast/test_classes/test_yield_inside_init.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ def __init__(self, *args, **kwargs):
1919
yield self
2020
"""
2121

22+
regular_method_with_yield = """
23+
class ModuleMembersVisitor(object):
24+
def method(self, *args, **kwargs):
25+
yield self
26+
"""
27+
28+
iter_with_yield = """
29+
class ModuleMembersVisitor(object):
30+
def __iter__(self, *args, **kwargs):
31+
yield self
32+
"""
33+
2234
async_init_without_yield = """
2335
class ModuleMembersVisitor(object):
2436
async def __init__(self, *args, **kwargs):
@@ -54,6 +66,8 @@ def test_init_generator(
5466
@pytest.mark.parametrize('code', [
5567
init_without_yield,
5668
async_init_without_yield,
69+
regular_method_with_yield,
70+
iter_with_yield,
5771
])
5872
def test_init_regular(
5973
assert_errors,

tests/test_visitors/test_ast/test_complexity/test_jones/test_line_complexity.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,9 @@ def test_same_complexity(parse_ast_tree, default_options):
111111
simple_visitor.run()
112112
typed_visitor.run()
113113

114-
assert len(simple_visitor._lines) == 1
115-
assert len(simple_visitor._lines[1]) == 3
116-
assert len(simple_visitor._lines[1]) == len(typed_visitor._lines[1])
114+
assert len(simple_visitor._lines) == 1 # noqa: Z441
115+
assert len(simple_visitor._lines[1]) == 3 # noqa: Z441
116+
assert len(typed_visitor._lines[1]) == 3 # noqa: Z441
117117

118118

119119
@pytest.mark.parametrize('code, complexity', [
@@ -127,8 +127,8 @@ def test_exact_complexity(parse_ast_tree, default_options, code, complexity):
127127
visitor = JonesComplexityVisitor(default_options, tree=tree)
128128
visitor.run()
129129

130-
assert len(visitor._lines) == 1
131-
assert len(visitor._lines[1]) == complexity
130+
assert len(visitor._lines) == 1 # noqa: Z441
131+
assert len(visitor._lines[1]) == complexity # noqa: Z441
132132

133133

134134
@pytest.mark.parametrize('code, number_of_lines', [
@@ -147,4 +147,4 @@ def test_that_some_nodes_are_ignored(
147147
visitor = JonesComplexityVisitor(default_options, tree=tree)
148148
visitor.run()
149149

150-
assert len(visitor._lines) == number_of_lines
150+
assert len(visitor._lines) == number_of_lines # noqa: Z441

tests/test_visitors/test_ast/test_imports/test_protected_import.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import pytest
44

5-
from wemake_python_styleguide.violations.naming import ProtectedModuleViolation
5+
from wemake_python_styleguide.violations.best_practices import (
6+
ProtectedModuleViolation,
7+
)
68
from wemake_python_styleguide.visitors.ast.imports import WrongImportVisitor
79

810
import_public = 'import public'
@@ -19,11 +21,18 @@
1921
import_from_public,
2022
import_from_public_path,
2123
])
22-
def test_correct_import(assert_errors, parse_ast_tree, code, default_options):
24+
def test_correct_import(
25+
assert_errors,
26+
parse_ast_tree,
27+
code,
28+
default_options,
29+
):
2330
"""Testing that correct imports are allowed."""
2431
tree = parse_ast_tree(code)
32+
2533
visitor = WrongImportVisitor(default_options, tree=tree)
2634
visitor.run()
35+
2736
assert_errors(visitor, [])
2837

2938

@@ -32,9 +41,16 @@ def test_correct_import(assert_errors, parse_ast_tree, code, default_options):
3241
import_from_protected,
3342
import_from_protected_path,
3443
])
35-
def test_incorrect_import(assert_errors, parse_ast_tree, code, default_options):
44+
def test_incorrect_import(
45+
assert_errors,
46+
parse_ast_tree,
47+
code,
48+
default_options,
49+
):
3650
"""Testing that imports from protected modules are restricted."""
3751
tree = parse_ast_tree(code)
52+
3853
visitor = WrongImportVisitor(default_options, tree=tree)
3954
visitor.run()
55+
4056
assert_errors(visitor, [ProtectedModuleViolation])

0 commit comments

Comments
 (0)