flake8-elegant-objects 1.0.0__tar.gz → 1.1.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.
Files changed (51) hide show
  1. flake8_elegant_objects-1.1.1/.claude/settings.local.json +8 -0
  2. flake8_elegant_objects-1.1.1/CHANGELOG.md +190 -0
  3. flake8_elegant_objects-1.1.1/CLAUDE.md +96 -0
  4. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/PKG-INFO +67 -23
  5. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/README.md +65 -21
  6. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/flake8_elegant_objects/__init__.py +1 -1
  7. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/flake8_elegant_objects/base.py +24 -4
  8. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/flake8_elegant_objects/no_constructor_code.py +14 -0
  9. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/flake8_elegant_objects/no_er_name.py +1 -1
  10. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/flake8_elegant_objects/no_impure_tests.py +48 -38
  11. flake8_elegant_objects-1.1.1/flake8_elegant_objects/no_mutable_objects/__init__.py +5 -0
  12. flake8_elegant_objects-1.1.1/flake8_elegant_objects/no_mutable_objects/base.py +57 -0
  13. flake8_elegant_objects-1.1.1/flake8_elegant_objects/no_mutable_objects/contract_checker.py +76 -0
  14. flake8_elegant_objects-1.1.1/flake8_elegant_objects/no_mutable_objects/copy_on_write_checker.py +60 -0
  15. flake8_elegant_objects-1.1.1/flake8_elegant_objects/no_mutable_objects/core.py +306 -0
  16. flake8_elegant_objects-1.1.1/flake8_elegant_objects/no_mutable_objects/deep_checker.py +130 -0
  17. flake8_elegant_objects-1.1.1/flake8_elegant_objects/no_mutable_objects/factory_checker.py +91 -0
  18. flake8_elegant_objects-1.1.1/flake8_elegant_objects/no_mutable_objects/pattern_detectors.py +134 -0
  19. flake8_elegant_objects-1.1.1/flake8_elegant_objects/no_mutable_objects/shared_state_checker.py +32 -0
  20. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/flake8_elegant_objects/no_public_methods_without_contracts.py +1 -1
  21. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/pyproject.toml +12 -4
  22. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/tests/test_integration.py +7 -6
  23. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/tests/test_no_constructor_code.py +61 -0
  24. flake8_elegant_objects-1.1.1/tests/test_no_mutable_objects.py +468 -0
  25. flake8_elegant_objects-1.0.0/CLAUDE.md +0 -59
  26. flake8_elegant_objects-1.0.0/flake8_elegant_objects/no_mutable_objects.py +0 -69
  27. flake8_elegant_objects-1.0.0/tests/test_no_mutable_objects.py +0 -167
  28. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/.github/workflows/lint.yml +0 -0
  29. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/.github/workflows/publish.yml +0 -0
  30. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/.github/workflows/test.yml +0 -0
  31. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/.gitignore +0 -0
  32. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/LICENSE +0 -0
  33. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/docs/EO011.md +0 -0
  34. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/flake8_elegant_objects/__main__.py +0 -0
  35. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/flake8_elegant_objects/no_getters_setters.py +0 -0
  36. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/flake8_elegant_objects/no_implementation_inheritance.py +0 -0
  37. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/flake8_elegant_objects/no_null.py +0 -0
  38. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/flake8_elegant_objects/no_orm.py +0 -0
  39. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/flake8_elegant_objects/no_static.py +0 -0
  40. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/flake8_elegant_objects/no_type_discrimination.py +0 -0
  41. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/tests/__init__.py +0 -0
  42. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/tests/test_no_er_name.py +0 -0
  43. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/tests/test_no_getters_setters.py +0 -0
  44. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/tests/test_no_implementation_inheritance.py +0 -0
  45. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/tests/test_no_impure_tests.py +0 -0
  46. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/tests/test_no_null.py +0 -0
  47. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/tests/test_no_orm.py +0 -0
  48. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/tests/test_no_public_methods_without_contracts.py +0 -0
  49. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/tests/test_no_static.py +0 -0
  50. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/tests/test_no_type_discrimination.py +0 -0
  51. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.1}/uv.lock +0 -0
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(ruff check:*)"
5
+ ],
6
+ "deny": []
7
+ }
8
+ }
@@ -0,0 +1,190 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.1.1] - 2024-06-30
9
+
10
+ ### Minor release
11
+
12
+ - `EO006`: Allow `super()` calls in constructor
13
+
14
+ ## [1.1.0] - 2024-06-28
15
+
16
+ ### 🚀 Major Enhancements
17
+
18
+ #### Enhanced Mutable Objects Detection
19
+
20
+ - **Expanded EO008**: Complete rewrite from single checker to comprehensive modular package
21
+ - **12 New Error Codes**: Added EO015-EO027 for granular mutability detection
22
+ - `EO015`: Mutable class attribute violations
23
+ - `EO016`: Mutable instance attribute violations
24
+ - `EO017`: Instance attribute mutation violations
25
+ - `EO018`: Augmented assignment mutation violations
26
+ - `EO019`: Mutating method call violations
27
+ - `EO020`: Subscript assignment mutation violations
28
+ - `EO021`: Chained mutation violations
29
+ - `EO022`: Missing factory methods violations
30
+ - `EO023`: Mutable default argument violations
31
+ - `EO024`: Missing immutability enforcement violations
32
+ - `EO025`: Copy-on-write violations
33
+ - `EO026`: Aliasing violations (exposing mutable state)
34
+ - `EO027`: Defensive copy violations
35
+
36
+ #### Modular Architecture
37
+
38
+ - **6 Specialized Checkers**: Organized mutable objects detection into focused modules
39
+ - `core.py`: Main orchestrator coordinating all sub-checkers
40
+ - `contract_checker.py`: Immutability contract enforcement
41
+ - `copy_on_write_checker.py`: Copy-on-write pattern validation
42
+ - `deep_checker.py`: Cross-class mutation analysis
43
+ - `factory_checker.py`: Factory method pattern validation
44
+ - `pattern_detectors.py`: Aliasing and defensive copy detection
45
+ - `shared_state_checker.py`: Shared mutable state detection
46
+
47
+ ### 🔧 Code Quality & Complexity Rules
48
+
49
+ #### Ruff Integration
50
+
51
+ - **McCabe Complexity**: Added `C90` with max-complexity = 10
52
+ - **Nested Blocks**: Added `PLR1702` with max-nested-blocks = 5
53
+ - **Set Literals**: Added `PLR6201` enforcement (tuple → set membership tests)
54
+ - **Preview Mode**: Enabled ruff preview for latest pylint rules
55
+
56
+ #### Code Refactoring
57
+
58
+ - **Reduced Complexity**: Refactored methods from 6-7 nesting levels to ≤5
59
+ - **Extract Methods**: Improved maintainability through helper method extraction
60
+ - **Static Methods**: Added `@staticmethod` decorators where appropriate
61
+ - **Type Safety**: Enhanced mypy compliance with proper type narrowing
62
+
63
+ ### 📈 Testing & Coverage
64
+
65
+ #### Coverage Improvements
66
+
67
+ - **90% Coverage**: Increased from 86% to 90% (+5% improvement)
68
+ - **Pattern Detectors**: From 0% to 99% coverage
69
+ - **New Test Cases**: Added 4 comprehensive test cases for EO026/EO027
70
+ - **Integration Tests**: Enhanced end-to-end testing for new checkers
71
+
72
+ #### Test Quality
73
+
74
+ - **100 Tests**: All tests pass with enhanced coverage
75
+ - **False Positive Prevention**: Added tests ensuring clean code doesn't trigger violations
76
+ - **Real-world Scenarios**: Added aliasing and defensive copy test cases
77
+
78
+ ### 🏗️ Architecture Improvements
79
+
80
+ #### Type Safety
81
+
82
+ - **MyPy Clean**: Fixed all type errors with proper AsyncFunctionDef support
83
+ - **Type Assertions**: Added strategic type narrowing for complex AST patterns
84
+ - **Null Safety**: Enhanced null checking for optional attributes
85
+
86
+ #### Documentation
87
+
88
+ - **Updated README**: Comprehensive error code documentation with examples
89
+ - **Project Structure**: Updated to reflect new modular architecture
90
+ - **Coverage Badge**: Updated to reflect improved test coverage (90%)
91
+
92
+ ### 🐛 Bug Fixes
93
+
94
+ - **Pattern Detector Integration**: Fixed unused pattern detectors (0% → 99% coverage)
95
+ - **Type Compatibility**: Resolved mypy errors with union types
96
+ - **Whitespace Handling**: Consistent code formatting across all files
97
+
98
+ ### ⚠️ Breaking Changes
99
+
100
+ - **Mutable Objects**: EO008 now focuses specifically on dataclass violations
101
+ - **New Error Codes**: Code that was previously missed may now trigger EO015-EO027
102
+
103
+ ### 🔄 Internal Changes
104
+
105
+ - **Ruff Configuration**: Added comprehensive linting rules with complexity enforcement
106
+ - **Parent Tracking**: Enhanced AST analysis with parent node mapping
107
+ - **Modular Imports**: Reorganized imports for better dependency management
108
+
109
+ ---
110
+
111
+ ## [1.0.0] - 2024-06-21
112
+
113
+ ### 🎉 Initial Release
114
+
115
+ This is the first release of `flake8-elegant-objects`, a comprehensive Flake8 plugin that enforces the core principles of Elegant Objects programming philosophy by Yegor Bugayenko.
116
+
117
+ ### ✨ Features
118
+
119
+ #### Error Codes Implemented
120
+
121
+ - **EO001-EO004**: No "-er" naming principle violations
122
+
123
+ - `EO001`: Class names ending in "-er" (Manager, Controller, Handler, etc.)
124
+ - `EO002`: Method names ending in "-er" (process → processing)
125
+ - `EO003`: Variable names ending in "-er" (parser → arguments)
126
+ - `EO004`: Function names ending in "-er" (analyzer → analysis)
127
+
128
+ - **EO005**: No null (None) usage - prevents defensive programming and unclear contracts
129
+
130
+ - **EO006**: No code in constructors - constructors should only assemble objects, not execute logic
131
+
132
+ - **EO007**: No getters/setters - prevents data container anti-pattern, promotes behavioral objects
133
+
134
+ - **EO008**: No mutable objects - enforces immutability for thread-safety and predictability
135
+
136
+ - **EO009**: No static methods - promotes proper object-oriented design over procedural programming
137
+
138
+ - **EO010**: No type discrimination - prevents isinstance/type casting that violates polymorphism
139
+
140
+ - **EO011**: No public methods without contracts - requires Protocol/ABC contracts for public methods
141
+
142
+ - **EO012**: Test methods should only contain assertThat statements - enforces focused, single-assertion tests
143
+
144
+ - **EO013**: No ORM/ActiveRecord patterns - prevents mixing persistence with business logic
145
+
146
+ - **EO014**: No implementation inheritance - promotes composition over inheritance
147
+
148
+ #### 🔧 Technical Details
149
+
150
+ ##### Requirements
151
+
152
+ - Python 3.10+
153
+ - AST-based analysis for accurate code inspection
154
+ - Zero runtime dependencies beyond Python standard library
155
+
156
+ ##### Code Quality
157
+
158
+ - **86% Test Coverage**: Comprehensive test suite with real-world examples
159
+ - **Type Safety**: Full mypy type checking compliance
160
+ - **Code Style**: Ruff formatting and linting
161
+ - **Documentation**: Extensive examples and principle explanations
162
+
163
+ ##### Performance
164
+
165
+ - **Efficient AST Traversal**: Optimized single-pass analysis
166
+ - **Minimal Memory Usage**: Lightweight violation tracking
167
+ - **Fast Execution**: Suitable for large codebases
168
+
169
+ #### 🧪 Testing
170
+
171
+ - **14 Test Modules**: One dedicated test file per principle
172
+ - **Real-world Examples**: Test cases based on actual code patterns
173
+ - **Integration Tests**: End-to-end plugin functionality verification
174
+ - **Continuous Testing**: Automated test execution with coverage reporting
175
+
176
+ #### 🚀 Usage Examples
177
+
178
+ ```bash
179
+ # Install
180
+ pip install flake8-elegant-objects
181
+
182
+ # Use as flake8 plugin
183
+ flake8 --select=EO your_code/
184
+
185
+ # Run standalone
186
+ python -m flake8_elegant_objects your_code/
187
+
188
+ # Configuration
189
+ echo "[flake8]\nselect = E,W,F,EO" > .flake8
190
+ ```
@@ -0,0 +1,96 @@
1
+ # CLAUDE.md
2
+
3
+ ## Development Commands
4
+
5
+ ```bash
6
+ # Testing
7
+ python -m pytest tests/ -v
8
+
9
+ # Code Quality
10
+ mypy flake8_elegant_objects/
11
+ ruff check flake8_elegant_objects/
12
+ ruff format flake8_elegant_objects/
13
+
14
+ # Plugin Usage
15
+ python -m flake8_elegant_objects --show-source path/to/files/*.py
16
+ flake8 --select=EO path/to/files/
17
+ ```
18
+
19
+ ## Architecture
20
+
21
+ Flake8 plugin enforcing Elegant Objects principles (27 error codes EO001-EO027).
22
+
23
+ **Core Components:**
24
+ - `__init__.py`: ElegantObjectsPlugin orchestrating analysis
25
+ - `base.py`: Principles base class, Violations type, ErrorCodes (EO001-EO027)
26
+ - `no_er_name.py`: NoErName (EO001-EO004)
27
+ - `no_mutable_objects.py`: NoMutableObjects (EO008, EO015-EO027)
28
+ - Other principle checkers: (EO005-EO007, EO009-EO014)
29
+
30
+ **Pattern:** Plugin uses Principles which provides Violations (no None)
31
+
32
+ ## Error Codes
33
+
34
+ ### Naming Violations (EO001-EO004)
35
+ - **EO001**: Class name violates -er principle
36
+ - **EO002**: Method name violates -er principle
37
+ - **EO003**: Variable name violates -er principle
38
+ - **EO004**: Function name violates -er principle
39
+
40
+ ### Object Behavior (EO005-EO007)
41
+ - **EO005**: Null (None) usage
42
+ - **EO006**: Code in constructor
43
+ - **EO007**: Getter/setter methods
44
+
45
+ ### Mutable Object Violations (EO008, EO015-EO027)
46
+ - **EO008**: Mutable dataclass violation
47
+ - **EO015**: Mutable class attribute violation
48
+ - **EO016**: Mutable instance attribute violation
49
+ - **EO017**: Instance attribute mutation violation
50
+ - **EO018**: Augmented assignment mutation violation
51
+ - **EO019**: Mutating method call violation
52
+ - **EO020**: Subscript assignment mutation violation
53
+ - **EO021**: Chained mutation violation
54
+ - **EO022**: Missing factory methods violation
55
+ - **EO023**: Mutable default argument violation
56
+ - **EO024**: Missing immutability enforcement violation
57
+ - **EO025**: Copy-on-write violation
58
+ - **EO026**: Aliasing violation (exposing mutable state)
59
+ - **EO027**: Defensive copy violation
60
+
61
+ ### Design and Architecture (EO009-EO014)
62
+ - **EO009**: Static methods
63
+ - **EO010**: isinstance/type casting
64
+ - **EO011**: Public methods without contracts
65
+ - **EO012**: Test methods with non-assertThat statements
66
+ - **EO013**: ORM/ActiveRecord patterns
67
+ - **EO014**: Implementation inheritance
68
+
69
+ ## Elegant Objects Principles
70
+
71
+ **MUST follow:**
72
+ - No null (None) - use empty lists instead
73
+ - No code in constructors
74
+ - No getters/setters
75
+ - No mutable objects
76
+ - NO "-ER" NAMES: Manager, Controller, Helper, Handler, Parser, etc
77
+ - No static methods
78
+ - No instanceof/type casting
79
+ - No public methods without contracts
80
+ - No statements in test methods except assertThat
81
+ - No ORM/ActiveRecord
82
+ - No implementation inheritance
83
+
84
+ **Philosophy:**
85
+ - Objects are living partners, not data containers
86
+ - Declarative over imperative (`new Sorted(apples)` not `new Sorter().sort(apples)`)
87
+ - Fail fast with exceptions, not null checks
88
+ - Composition over inheritance
89
+ - Immutability implied by design, not keywords
90
+
91
+ ## Code Style
92
+
93
+ - No useless comments
94
+ - Types: `Violations = list[Violation]`
95
+ - Return empty lists `[]` instead of `None`
96
+ - Extend lists instead of checking for None
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flake8-elegant-objects
3
- Version: 1.0.0
3
+ Version: 1.1.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
7
7
  Project-URL: Issues, https://github.com/AntonProkopyev/flake8-elegant-objects/issues
8
8
  Project-URL: Documentation, https://github.com/AntonProkopyev/flake8-elegant-objects#readme
9
- Author: Flake8 Elegant Objects Contributors
9
+ Author: Anton Prokopyev
10
10
  License: MIT
11
11
  License-File: LICENSE
12
12
  Keywords: code-quality,elegant-objects,flake8,linting,object-oriented,python,static-analysis
@@ -31,25 +31,45 @@ Description-Content-Type: text/markdown
31
31
  # Flake8 ElegantObjects Plugin
32
32
 
33
33
  [![Tests](https://img.shields.io/badge/tests-passing-brightgreen.svg)](https://github.com/AntonProkopyev/flake8-elegant-objects)
34
- [![Coverage](https://img.shields.io/badge/coverage-86%25-brightgreen.svg)](https://github.com/AntonProkopyev/flake8-elegant-objects)
34
+ [![Coverage](https://img.shields.io/badge/coverage-90%25-brightgreen.svg)](https://github.com/AntonProkopyev/flake8-elegant-objects)
35
35
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://python.org)
36
36
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
37
37
  [![Code style: ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
38
38
  [![Type checked: mypy](https://img.shields.io/badge/type_checked-mypy-blue.svg)](https://mypy.readthedocs.io/)
39
39
  [![Flake8](https://img.shields.io/badge/flake8-plugin-orange.svg)](https://flake8.pycqa.org/)
40
40
 
41
- Detects violations of core Elegant Objects principles including the "-er" naming principle, null usage, mutable objects, code in constructors, and getter/setter patterns.
41
+ Detects violations of [Elegant Objects principles](https://www.elegantobjects.org/) including the "-er" naming principle, null usage, mutable objects, code in constructors, and getter/setter patterns.
42
42
 
43
43
  ## Error Codes
44
44
 
45
+ ### Naming Violations (EO001-EO004)
45
46
  - `EO001`: Class name violates -er principle
46
47
  - `EO002`: Method name violates -er principle
47
48
  - `EO003`: Variable name violates -er principle
48
49
  - `EO004`: Function name violates -er principle
50
+
51
+ ### Object Behavior (EO005-EO007)
49
52
  - `EO005`: Null (None) usage violates EO principle
50
53
  - `EO006`: Code in constructor violates EO principle
51
54
  - `EO007`: Getter/setter method violates EO principle
52
- - `EO008`: Mutable object violation
55
+
56
+ ### Mutable Object Violations (EO008, EO015-EO027)
57
+ - `EO008`: Mutable dataclass violation
58
+ - `EO015`: Mutable class attribute violation
59
+ - `EO016`: Mutable instance attribute violation
60
+ - `EO017`: Instance attribute mutation violation
61
+ - `EO018`: Augmented assignment mutation violation
62
+ - `EO019`: Mutating method call violation
63
+ - `EO020`: Subscript assignment mutation violation
64
+ - `EO021`: Chained mutation violation
65
+ - `EO022`: Missing factory methods violation
66
+ - `EO023`: Mutable default argument violation
67
+ - `EO024`: Missing immutability enforcement violation
68
+ - `EO025`: Copy-on-write violation
69
+ - `EO026`: Aliasing violation (exposing mutable state)
70
+ - `EO027`: Defensive copy violation
71
+
72
+ ### Design and Architecture (EO009-EO014)
53
73
  - `EO009`: Static method violates EO principle (no static methods allowed)
54
74
  - `EO010`: isinstance/type casting violates EO principle (avoid type discrimination)
55
75
  - `EO011`: Public method without contract (Protocol/ABC) violates EO principle
@@ -82,7 +102,7 @@ The plugin is automatically registered when the package is installed.
82
102
 
83
103
  ## Philosophy
84
104
 
85
- Based on Yegor Bugayenko's Elegant Objects principles, this plugin enforces object-oriented design that treats objects as living, thinking entities rather than data containers or procedure executors.
105
+ Based on [Yegor Bugayenko](https://www.yegor256.com/)'s [Elegant Objects principles](https://www.elegantobjects.org/), this plugin enforces object-oriented design that treats objects as living, thinking entities rather than data containers or procedure executors.
86
106
 
87
107
  ### 1. No "-er" Entities (EO001-EO004)
88
108
 
@@ -113,12 +133,26 @@ Based on Yegor Bugayenko's Elegant Objects principles, this plugin enforces obje
113
133
  - ❌ `def get_value()` / `def set_value()` → ✅ Objects should expose behavior, not data
114
134
  - ❌ `user.getName()` → ✅ `user.introduce_yourself()` or `user.greet(visitor)`
115
135
 
116
- ### 5. No Mutable Objects (EO008)
136
+ ### 5. No Mutable Objects (EO008, EO015-EO027)
137
+
138
+ **Why?** Mutable objects introduce temporal coupling and make reasoning about code difficult. Immutable objects are thread-safe, predictable, and easier to test. This plugin provides comprehensive detection of various mutability patterns.
139
+
140
+ **Basic Mutability Issues:**
141
+ - ❌ `@dataclass class Data` → ✅ `@dataclass(frozen=True) class Data` *(EO008)*
142
+ - ❌ `items = []` (class attribute) → ✅ `items: tuple = ()` *(EO015)*
143
+ - ❌ `self.data = []` (instance attribute) → ✅ `self.data: tuple = ()` *(EO016)*
117
144
 
118
- **Why?** Mutable objects introduce temporal coupling and make reasoning about code difficult. Immutable objects are thread-safe, predictable, and easier to test.
145
+ **Mutation Patterns:**
146
+ - ❌ `self.items.append(x)` → ✅ `self.items = (*self.items, x)` *(EO019)*
147
+ - ❌ `self.count += 1` → ✅ `return Counter(self.count + 1)` *(EO018)*
148
+ - ❌ `self.data[key] = value` → ✅ Use immutable data structures *(EO020)*
149
+ - ❌ `self.data = new_value` (after init) → ✅ Return new instance *(EO017)*
119
150
 
120
- - ❌ `@dataclass class Data` → ✅ `@dataclass(frozen=True) class Data`
121
- - ❌ `items = []` → ✅ `items: tuple = ()`
151
+ **Advanced Patterns:**
152
+ - ❌ `def items=[]:` (mutable defaults) → ✅ `def items=None:` + null object *(EO023)*
153
+ - ❌ `return self._items` (exposing mutable state) → ✅ `return tuple(self._items)` *(EO026)*
154
+ - ❌ `self.items = items` (no defensive copy) → ✅ `self.items = tuple(items)` *(EO027)*
155
+ - ❌ Class with mutable state but no factory methods → ✅ Provide immutable factory methods *(EO022)*
122
156
 
123
157
  ### 6. No Static Methods (EO009)
124
158
 
@@ -200,17 +234,27 @@ ruff format flake8_elegant_objects/
200
234
 
201
235
  ```
202
236
  flake8_elegant_objects/
203
- ├── __init__.py # Main plugin entry point
204
- ├── base.py # Base classes and utilities
205
- ├── no_er_name.py # EO001-EO004: No "-er" names
206
- ├── no_null.py # EO005: No None usage
207
- ├── no_constructor_code.py # EO006: No code in constructors
208
- ├── no_getters_setters.py # EO007: No getters/setters
209
- ├── no_mutable_objects.py # EO008: No mutable objects
210
- ├── no_static.py # EO009: No static methods
211
- ├── no_type_discrimination.py # EO010: No isinstance/type casting
212
- ├── no_public_methods_without_contracts.py # EO011: Contracts required
213
- ├── no_impure_tests.py # EO012: Only assertThat in tests
214
- ├── no_orm.py # EO013: No ORM/ActiveRecord
215
- └── no_implementation_inheritance.py # EO014: No implementation inheritance
237
+ ├── __init__.py # Main plugin entry point
238
+ ├── __main__.py # CLI interface
239
+ ├── base.py # Core types, error codes, and base classes
240
+ ├── no_constructor_code.py # EO006: No code in constructors
241
+ ├── no_er_name.py # EO001-EO004: No -er naming violations
242
+ ├── no_getters_setters.py # EO007: No getter/setter methods
243
+ ├── no_implementation_inheritance.py # EO014: No implementation inheritance
244
+ ├── no_impure_tests.py # EO012: Test methods with single assertions
245
+ ├── no_null.py # EO005: No None/null usage
246
+ ├── no_orm.py # EO013: No ORM/ActiveRecord patterns
247
+ ├── no_public_methods_without_contracts.py # EO011: Methods need contracts
248
+ ├── no_static.py # EO009: No static methods
249
+ ├── no_type_discrimination.py # EO010: No isinstance/type casting
250
+ └── no_mutable_objects/ # EO008, EO015-EO027: Comprehensive mutability detection
251
+ ├── __init__.py # Package initialization
252
+ ├── base.py # Shared utilities and state tracking
253
+ ├── core.py # Main orchestrator for all mutable object checks
254
+ ├── contract_checker.py # EO024: Immutability contract enforcement
255
+ ├── copy_on_write_checker.py # EO025: Copy-on-write pattern validation
256
+ ├── deep_checker.py # Cross-class mutation analysis
257
+ ├── factory_checker.py # EO022: Factory method pattern validation
258
+ ├── pattern_detectors.py # EO026-EO027: Aliasing and defensive copy detection
259
+ └── shared_state_checker.py # EO023: Shared mutable state detection
216
260
  ```
@@ -1,25 +1,45 @@
1
1
  # Flake8 ElegantObjects Plugin
2
2
 
3
3
  [![Tests](https://img.shields.io/badge/tests-passing-brightgreen.svg)](https://github.com/AntonProkopyev/flake8-elegant-objects)
4
- [![Coverage](https://img.shields.io/badge/coverage-86%25-brightgreen.svg)](https://github.com/AntonProkopyev/flake8-elegant-objects)
4
+ [![Coverage](https://img.shields.io/badge/coverage-90%25-brightgreen.svg)](https://github.com/AntonProkopyev/flake8-elegant-objects)
5
5
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://python.org)
6
6
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
7
  [![Code style: ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
8
8
  [![Type checked: mypy](https://img.shields.io/badge/type_checked-mypy-blue.svg)](https://mypy.readthedocs.io/)
9
9
  [![Flake8](https://img.shields.io/badge/flake8-plugin-orange.svg)](https://flake8.pycqa.org/)
10
10
 
11
- Detects violations of core Elegant Objects principles including the "-er" naming principle, null usage, mutable objects, code in constructors, and getter/setter patterns.
11
+ Detects violations of [Elegant Objects principles](https://www.elegantobjects.org/) including the "-er" naming principle, null usage, mutable objects, code in constructors, and getter/setter patterns.
12
12
 
13
13
  ## Error Codes
14
14
 
15
+ ### Naming Violations (EO001-EO004)
15
16
  - `EO001`: Class name violates -er principle
16
17
  - `EO002`: Method name violates -er principle
17
18
  - `EO003`: Variable name violates -er principle
18
19
  - `EO004`: Function name violates -er principle
20
+
21
+ ### Object Behavior (EO005-EO007)
19
22
  - `EO005`: Null (None) usage violates EO principle
20
23
  - `EO006`: Code in constructor violates EO principle
21
24
  - `EO007`: Getter/setter method violates EO principle
22
- - `EO008`: Mutable object violation
25
+
26
+ ### Mutable Object Violations (EO008, EO015-EO027)
27
+ - `EO008`: Mutable dataclass violation
28
+ - `EO015`: Mutable class attribute violation
29
+ - `EO016`: Mutable instance attribute violation
30
+ - `EO017`: Instance attribute mutation violation
31
+ - `EO018`: Augmented assignment mutation violation
32
+ - `EO019`: Mutating method call violation
33
+ - `EO020`: Subscript assignment mutation violation
34
+ - `EO021`: Chained mutation violation
35
+ - `EO022`: Missing factory methods violation
36
+ - `EO023`: Mutable default argument violation
37
+ - `EO024`: Missing immutability enforcement violation
38
+ - `EO025`: Copy-on-write violation
39
+ - `EO026`: Aliasing violation (exposing mutable state)
40
+ - `EO027`: Defensive copy violation
41
+
42
+ ### Design and Architecture (EO009-EO014)
23
43
  - `EO009`: Static method violates EO principle (no static methods allowed)
24
44
  - `EO010`: isinstance/type casting violates EO principle (avoid type discrimination)
25
45
  - `EO011`: Public method without contract (Protocol/ABC) violates EO principle
@@ -52,7 +72,7 @@ The plugin is automatically registered when the package is installed.
52
72
 
53
73
  ## Philosophy
54
74
 
55
- Based on Yegor Bugayenko's Elegant Objects principles, this plugin enforces object-oriented design that treats objects as living, thinking entities rather than data containers or procedure executors.
75
+ Based on [Yegor Bugayenko](https://www.yegor256.com/)'s [Elegant Objects principles](https://www.elegantobjects.org/), this plugin enforces object-oriented design that treats objects as living, thinking entities rather than data containers or procedure executors.
56
76
 
57
77
  ### 1. No "-er" Entities (EO001-EO004)
58
78
 
@@ -83,12 +103,26 @@ Based on Yegor Bugayenko's Elegant Objects principles, this plugin enforces obje
83
103
  - ❌ `def get_value()` / `def set_value()` → ✅ Objects should expose behavior, not data
84
104
  - ❌ `user.getName()` → ✅ `user.introduce_yourself()` or `user.greet(visitor)`
85
105
 
86
- ### 5. No Mutable Objects (EO008)
106
+ ### 5. No Mutable Objects (EO008, EO015-EO027)
107
+
108
+ **Why?** Mutable objects introduce temporal coupling and make reasoning about code difficult. Immutable objects are thread-safe, predictable, and easier to test. This plugin provides comprehensive detection of various mutability patterns.
109
+
110
+ **Basic Mutability Issues:**
111
+ - ❌ `@dataclass class Data` → ✅ `@dataclass(frozen=True) class Data` *(EO008)*
112
+ - ❌ `items = []` (class attribute) → ✅ `items: tuple = ()` *(EO015)*
113
+ - ❌ `self.data = []` (instance attribute) → ✅ `self.data: tuple = ()` *(EO016)*
87
114
 
88
- **Why?** Mutable objects introduce temporal coupling and make reasoning about code difficult. Immutable objects are thread-safe, predictable, and easier to test.
115
+ **Mutation Patterns:**
116
+ - ❌ `self.items.append(x)` → ✅ `self.items = (*self.items, x)` *(EO019)*
117
+ - ❌ `self.count += 1` → ✅ `return Counter(self.count + 1)` *(EO018)*
118
+ - ❌ `self.data[key] = value` → ✅ Use immutable data structures *(EO020)*
119
+ - ❌ `self.data = new_value` (after init) → ✅ Return new instance *(EO017)*
89
120
 
90
- - ❌ `@dataclass class Data` → ✅ `@dataclass(frozen=True) class Data`
91
- - ❌ `items = []` → ✅ `items: tuple = ()`
121
+ **Advanced Patterns:**
122
+ - ❌ `def items=[]:` (mutable defaults) → ✅ `def items=None:` + null object *(EO023)*
123
+ - ❌ `return self._items` (exposing mutable state) → ✅ `return tuple(self._items)` *(EO026)*
124
+ - ❌ `self.items = items` (no defensive copy) → ✅ `self.items = tuple(items)` *(EO027)*
125
+ - ❌ Class with mutable state but no factory methods → ✅ Provide immutable factory methods *(EO022)*
92
126
 
93
127
  ### 6. No Static Methods (EO009)
94
128
 
@@ -170,17 +204,27 @@ ruff format flake8_elegant_objects/
170
204
 
171
205
  ```
172
206
  flake8_elegant_objects/
173
- ├── __init__.py # Main plugin entry point
174
- ├── base.py # Base classes and utilities
175
- ├── no_er_name.py # EO001-EO004: No "-er" names
176
- ├── no_null.py # EO005: No None usage
177
- ├── no_constructor_code.py # EO006: No code in constructors
178
- ├── no_getters_setters.py # EO007: No getters/setters
179
- ├── no_mutable_objects.py # EO008: No mutable objects
180
- ├── no_static.py # EO009: No static methods
181
- ├── no_type_discrimination.py # EO010: No isinstance/type casting
182
- ├── no_public_methods_without_contracts.py # EO011: Contracts required
183
- ├── no_impure_tests.py # EO012: Only assertThat in tests
184
- ├── no_orm.py # EO013: No ORM/ActiveRecord
185
- └── no_implementation_inheritance.py # EO014: No implementation inheritance
207
+ ├── __init__.py # Main plugin entry point
208
+ ├── __main__.py # CLI interface
209
+ ├── base.py # Core types, error codes, and base classes
210
+ ├── no_constructor_code.py # EO006: No code in constructors
211
+ ├── no_er_name.py # EO001-EO004: No -er naming violations
212
+ ├── no_getters_setters.py # EO007: No getter/setter methods
213
+ ├── no_implementation_inheritance.py # EO014: No implementation inheritance
214
+ ├── no_impure_tests.py # EO012: Test methods with single assertions
215
+ ├── no_null.py # EO005: No None/null usage
216
+ ├── no_orm.py # EO013: No ORM/ActiveRecord patterns
217
+ ├── no_public_methods_without_contracts.py # EO011: Methods need contracts
218
+ ├── no_static.py # EO009: No static methods
219
+ ├── no_type_discrimination.py # EO010: No isinstance/type casting
220
+ └── no_mutable_objects/ # EO008, EO015-EO027: Comprehensive mutability detection
221
+ ├── __init__.py # Package initialization
222
+ ├── base.py # Shared utilities and state tracking
223
+ ├── core.py # Main orchestrator for all mutable object checks
224
+ ├── contract_checker.py # EO024: Immutability contract enforcement
225
+ ├── copy_on_write_checker.py # EO025: Copy-on-write pattern validation
226
+ ├── deep_checker.py # Cross-class mutation analysis
227
+ ├── factory_checker.py # EO022: Factory method pattern validation
228
+ ├── pattern_detectors.py # EO026-EO027: Aliasing and defensive copy detection
229
+ └── shared_state_checker.py # EO023: Shared mutable state detection
186
230
  ```
@@ -17,7 +17,7 @@ class ElegantObjectsPlugin:
17
17
  """Flake8 plugin to check for Elegant Objects violations."""
18
18
 
19
19
  name = "flake8-elegant-objects"
20
- version = "1.0.0"
20
+ version = "1.1.1"
21
21
 
22
22
  def __init__(self, tree: ast.AST) -> None:
23
23
  self.tree = tree
@@ -21,7 +21,7 @@ class ErrorCodes:
21
21
  EO005 = "EO005 Null (None) usage violates EO principle (avoid None)"
22
22
  EO006 = "EO006 Code in constructor violates EO principle (constructors should only assign parameters)"
23
23
  EO007 = "EO007 Getter/setter method '{name}' violates EO principle (avoid getters/setters)"
24
- EO008 = "EO008 Mutable object violation: '{name}' should be immutable"
24
+ EO008 = "EO008 Mutable dataclass violation: {name}"
25
25
  EO009 = (
26
26
  "EO009 Static method '{name}' violates EO principle (no static methods allowed)"
27
27
  )
@@ -30,6 +30,19 @@ class ErrorCodes:
30
30
  EO012 = "EO012 Test method '{name}' contains non-assertThat statements (only assertThat allowed)"
31
31
  EO013 = "EO013 ORM/ActiveRecord pattern '{name}' violates EO principle"
32
32
  EO014 = "EO014 Implementation inheritance violates EO principle (class '{name}' inherits from non-abstract class)"
33
+ EO015 = "EO015 Mutable class attribute violation: {name}"
34
+ EO016 = "EO016 Mutable instance attribute violation: {name}"
35
+ EO017 = "EO017 Instance attribute mutation violation: {name}"
36
+ EO018 = "EO018 Augmented assignment mutation violation: {name}"
37
+ EO019 = "EO019 Mutating method call violation: {name}"
38
+ EO020 = "EO020 Subscript assignment mutation violation: {name}"
39
+ EO021 = "EO021 Chained mutation violation: {name}"
40
+ EO022 = "EO022 Missing factory methods violation: {name}"
41
+ EO023 = "EO023 Mutable default argument violation: {name}"
42
+ EO024 = "EO024 Missing immutability enforcement violation: {name}"
43
+ EO025 = "EO025 Copy-on-write violation: {name}"
44
+ EO026 = "EO026 Aliasing violation (exposing mutable state): {name}"
45
+ EO027 = "EO027 Defensive copy violation: {name}"
33
46
 
34
47
 
35
48
  class Violation:
@@ -101,7 +114,7 @@ def is_method(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
101
114
  """Check if function is a method (has self parameter)."""
102
115
  if not node.args.args:
103
116
  return False
104
- return node.args.args[0].arg in ("self", "cls")
117
+ return node.args.args[0].arg in {"self", "cls"}
105
118
 
106
119
 
107
120
  def get_all_principles() -> list[Principle]:
@@ -139,6 +152,15 @@ class ElegantObjectsCore:
139
152
 
140
153
  def __init__(self, tree: ast.AST) -> None:
141
154
  self.tree = tree
155
+ self._parent_map: dict[ast.AST, ast.AST | None] = {}
156
+ self._build_parent_map(tree, None)
157
+
158
+ def _build_parent_map(self, node: ast.AST, parent: ast.AST | None) -> None:
159
+ """Build a map of nodes to their parents for better context."""
160
+ if parent:
161
+ setattr(node, "_parent", parent) # noqa: B010
162
+ for child in ast.iter_child_nodes(node):
163
+ self._build_parent_map(child, node)
142
164
 
143
165
  def check_violations(self) -> list[Violation]:
144
166
  """Check for all violations in the AST tree."""
@@ -156,10 +178,8 @@ class ElegantObjectsCore:
156
178
  if isinstance(node, ast.ClassDef):
157
179
  current_class = node
158
180
 
159
- # Check principles on current node
160
181
  violations.extend(self._check_principles(node, current_class))
161
182
 
162
- # Visit child nodes
163
183
  for child in ast.iter_child_nodes(node):
164
184
  violations.extend(self._visit(child, current_class))
165
185