flake8-elegant-objects 2.0.0__tar.gz → 2.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/PKG-INFO +1 -1
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/__init__.py +1 -1
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_impure_tests.py +14 -15
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_null.py +21 -27
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/pyproject.toml +1 -1
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_impure_tests.py +15 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_null.py +7 -2
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/uv.lock +1 -1
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/.github/workflows/lint.yml +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/.github/workflows/publish.yml +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/.github/workflows/test.yml +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/.gitignore +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/CHANGELOG.md +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/LICENSE +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/README.md +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/__main__.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/base.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/links.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_constructor_code.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_er_name.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_getters_setters.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_implementation_inheritance.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_mutable_objects/__init__.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_mutable_objects/base.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_mutable_objects/copy_on_write_checker.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_mutable_objects/core.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_mutable_objects/deep_checker.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_mutable_objects/pattern_detectors.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_mutable_objects/shared_state_checker.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_open_classes.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_orm.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_public_methods_without_contracts.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_static.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_type_discrimination.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/noqa.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/__init__.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_cli.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_flake8_plugin.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_integration.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_links.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_constructor_code.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_er_name.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_getters_setters.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_implementation_inheritance.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_mutable_objects.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_open_classes.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_orm.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_public_methods_without_contracts.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_static.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_type_discrimination.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_noqa.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_packaging.py +0 -0
- {flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_self_check.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flake8-elegant-objects
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: Flake8 plugin enforcing Elegant Objects principles: no -er naming, no null, no getters/setters, immutable objects
|
|
5
5
|
Project-URL: Homepage, https://github.com/AntonProkopyev/flake8-elegant-objects
|
|
6
6
|
Project-URL: Repository, https://github.com/AntonProkopyev/flake8-elegant-objects.git
|
|
@@ -146,25 +146,24 @@ class NoImpureTests(Principle):
|
|
|
146
146
|
"""Check if assertion exists anywhere in the call chain."""
|
|
147
147
|
current = call
|
|
148
148
|
while CALL.covers(current):
|
|
149
|
+
# A plain name is the head of the chain: there is nothing
|
|
150
|
+
# beneath it to descend into, so it answers and the walk ends.
|
|
151
|
+
# Left to loop, it would answer the same question forever.
|
|
149
152
|
if NAME.covers(current.func):
|
|
150
|
-
|
|
153
|
+
return (
|
|
151
154
|
current.func.id.startswith("assert")
|
|
152
155
|
or current.func.id == "assertThat"
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
elif ATTRIBUTE.covers(current.func):
|
|
156
|
-
if (
|
|
157
|
-
current.func.attr.startswith("assert")
|
|
158
|
-
or current.func.attr == "assertThat"
|
|
159
|
-
):
|
|
160
|
-
return True
|
|
161
|
-
# Move to the next level in the chain
|
|
162
|
-
if CALL.covers(current.func.value):
|
|
163
|
-
current = current.func.value
|
|
164
|
-
else:
|
|
165
|
-
break
|
|
166
|
-
else:
|
|
156
|
+
)
|
|
157
|
+
if not ATTRIBUTE.covers(current.func):
|
|
167
158
|
break
|
|
159
|
+
if (
|
|
160
|
+
current.func.attr.startswith("assert")
|
|
161
|
+
or current.func.attr == "assertThat"
|
|
162
|
+
):
|
|
163
|
+
return True
|
|
164
|
+
if not CALL.covers(current.func.value):
|
|
165
|
+
break
|
|
166
|
+
current = current.func.value
|
|
168
167
|
return False
|
|
169
168
|
|
|
170
169
|
def _is_assertion_context_manager(self, with_stmt: ast.With) -> bool:
|
{flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_null.py
RENAMED
|
@@ -5,7 +5,8 @@ from collections.abc import Iterator
|
|
|
5
5
|
from types import NoneType
|
|
6
6
|
from typing import final
|
|
7
7
|
|
|
8
|
-
from .base import EO005, REPORT, Instance, Principle, Source, Violations
|
|
8
|
+
from .base import EO005, REPORT, Instance, Parents, Principle, Source, Violations
|
|
9
|
+
from .base import NOTHING as ABSENT
|
|
9
10
|
|
|
10
11
|
ANN_ASSIGN = Instance(ast.AnnAssign)
|
|
11
12
|
ARG = Instance(ast.arg)
|
|
@@ -34,7 +35,7 @@ class NoNull(Principle):
|
|
|
34
35
|
return self._check_implicit_returns(node)
|
|
35
36
|
if CONSTANT.covers(node) and NOTHING.covers(node.value):
|
|
36
37
|
# Skip None in type annotations
|
|
37
|
-
if self.
|
|
38
|
+
if self._annotated(node, source.parents):
|
|
38
39
|
return []
|
|
39
40
|
return REPORT.of(node, EO005)
|
|
40
41
|
return []
|
|
@@ -64,30 +65,23 @@ class NoNull(Principle):
|
|
|
64
65
|
if RETURN.covers(inner):
|
|
65
66
|
yield inner
|
|
66
67
|
|
|
67
|
-
def
|
|
68
|
-
|
|
69
|
-
) -> bool:
|
|
70
|
-
"""Check if the target node is within a type annotation context."""
|
|
71
|
-
if not tree:
|
|
72
|
-
return False
|
|
73
|
-
|
|
74
|
-
# Find all annotation contexts in the tree
|
|
75
|
-
for node in ast.walk(tree):
|
|
76
|
-
# Function return annotations
|
|
77
|
-
if FUNCTION.covers(node) and node.returns:
|
|
78
|
-
if self._node_in_subtree(target_node, node.returns):
|
|
79
|
-
return True
|
|
80
|
-
# Parameter annotations
|
|
81
|
-
elif ARG.covers(node) and node.annotation:
|
|
82
|
-
if self._node_in_subtree(target_node, node.annotation):
|
|
83
|
-
return True
|
|
84
|
-
# Variable annotations
|
|
85
|
-
elif ANN_ASSIGN.covers(node) and node.annotation:
|
|
86
|
-
if self._node_in_subtree(target_node, node.annotation):
|
|
87
|
-
return True
|
|
68
|
+
def _annotated(self, node: ast.AST, parents: Parents) -> bool:
|
|
69
|
+
"""Whether this None sits inside a type annotation.
|
|
88
70
|
|
|
71
|
+
The question used to be asked downwards: walk the whole tree,
|
|
72
|
+
collect every annotation, look for the node inside it. That is the
|
|
73
|
+
tree once per None, and a file with hundreds of them pays for the
|
|
74
|
+
tree hundreds of times. Asked upwards, the answer costs the depth
|
|
75
|
+
of the nesting and nothing more.
|
|
76
|
+
"""
|
|
77
|
+
held = node
|
|
78
|
+
above = parents.above(held)
|
|
79
|
+
while above is not ABSENT:
|
|
80
|
+
if FUNCTION.covers(above) and above.returns is held:
|
|
81
|
+
return True
|
|
82
|
+
if ARG.covers(above) and above.annotation is held:
|
|
83
|
+
return True
|
|
84
|
+
if ANN_ASSIGN.covers(above) and above.annotation is held:
|
|
85
|
+
return True
|
|
86
|
+
held, above = above, parents.above(above)
|
|
89
87
|
return False
|
|
90
|
-
|
|
91
|
-
def _node_in_subtree(self, target: ast.AST, tree: ast.AST) -> bool:
|
|
92
|
-
"""Check if target node is within the tree."""
|
|
93
|
-
return any(child is target for child in ast.walk(tree))
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "flake8-elegant-objects"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.1"
|
|
8
8
|
description = "Flake8 plugin enforcing Elegant Objects principles: no -er naming, no null, no getters/setters, immutable objects"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -194,3 +194,18 @@ def test_cents_are_counted(self):
|
|
|
194
194
|
violations = self._check_code(code)
|
|
195
195
|
assert len(violations) == 1
|
|
196
196
|
assert "one assertion" in violations[0]
|
|
197
|
+
|
|
198
|
+
def test_call_on_a_call_terminates(self) -> None:
|
|
199
|
+
"""A chain headed by a plain call must not loop forever.
|
|
200
|
+
|
|
201
|
+
`helper().clear()` walks down to a Name that is not an assertion.
|
|
202
|
+
The walk once had no way out of that case and hung the whole run,
|
|
203
|
+
so this test is here to keep the exit.
|
|
204
|
+
"""
|
|
205
|
+
code = """
|
|
206
|
+
def test_cents_are_counted(self):
|
|
207
|
+
_loaded_tools().clear()
|
|
208
|
+
assert money.cents() == 100
|
|
209
|
+
"""
|
|
210
|
+
violations = self._check_code(code)
|
|
211
|
+
assert len(violations) == 1
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
|
|
5
|
-
from flake8_elegant_objects.base import Source
|
|
5
|
+
from flake8_elegant_objects.base import Parents, Source
|
|
6
6
|
from flake8_elegant_objects.no_null import NoNull
|
|
7
7
|
|
|
8
8
|
|
|
@@ -14,9 +14,14 @@ class TestNoNullPrinciple:
|
|
|
14
14
|
tree = ast.parse(code)
|
|
15
15
|
checker = NoNull()
|
|
16
16
|
violations = []
|
|
17
|
+
held: dict[ast.AST, ast.AST] = {}
|
|
18
|
+
for node in ast.walk(tree):
|
|
19
|
+
for child in ast.iter_child_nodes(node):
|
|
20
|
+
held[child] = node
|
|
21
|
+
parents = Parents(held)
|
|
17
22
|
|
|
18
23
|
def visit(node: ast.AST) -> None:
|
|
19
|
-
source = Source(node, None, tree)
|
|
24
|
+
source = Source(node, None, tree, parents)
|
|
20
25
|
violations.extend(checker.check(source))
|
|
21
26
|
for child in ast.iter_child_nodes(node):
|
|
22
27
|
visit(child)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/__main__.py
RENAMED
|
File without changes
|
{flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/base.py
RENAMED
|
File without changes
|
{flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/links.py
RENAMED
|
File without changes
|
|
File without changes
|
{flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_er_name.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_orm.py
RENAMED
|
File without changes
|
|
File without changes
|
{flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/no_static.py
RENAMED
|
File without changes
|
|
File without changes
|
{flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/flake8_elegant_objects/noqa.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_constructor_code.py
RENAMED
|
File without changes
|
|
File without changes
|
{flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_getters_setters.py
RENAMED
|
File without changes
|
|
File without changes
|
{flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_mutable_objects.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flake8_elegant_objects-2.0.0 → flake8_elegant_objects-2.0.1}/tests/test_no_type_discrimination.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|