flake8-elegant-objects 1.0.0__tar.gz → 1.1.0__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.0/.claude/settings.local.json +8 -0
  2. flake8_elegant_objects-1.1.0/CHANGELOG.md +184 -0
  3. flake8_elegant_objects-1.1.0/CLAUDE.md +96 -0
  4. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/PKG-INFO +67 -23
  5. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/README.md +65 -21
  6. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/base.py +24 -4
  7. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_er_name.py +1 -1
  8. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_impure_tests.py +48 -38
  9. flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/__init__.py +5 -0
  10. flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/base.py +57 -0
  11. flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/contract_checker.py +76 -0
  12. flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/copy_on_write_checker.py +60 -0
  13. flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/core.py +306 -0
  14. flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/deep_checker.py +130 -0
  15. flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/factory_checker.py +91 -0
  16. flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/pattern_detectors.py +134 -0
  17. flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/shared_state_checker.py +32 -0
  18. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_public_methods_without_contracts.py +1 -1
  19. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/pyproject.toml +12 -4
  20. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_integration.py +7 -6
  21. flake8_elegant_objects-1.1.0/tests/test_no_mutable_objects.py +468 -0
  22. flake8_elegant_objects-1.0.0/CLAUDE.md +0 -59
  23. flake8_elegant_objects-1.0.0/flake8_elegant_objects/no_mutable_objects.py +0 -69
  24. flake8_elegant_objects-1.0.0/tests/test_no_mutable_objects.py +0 -167
  25. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/.github/workflows/lint.yml +0 -0
  26. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/.github/workflows/publish.yml +0 -0
  27. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/.github/workflows/test.yml +0 -0
  28. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/.gitignore +0 -0
  29. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/LICENSE +0 -0
  30. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/docs/EO011.md +0 -0
  31. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/__init__.py +0 -0
  32. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/__main__.py +0 -0
  33. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_constructor_code.py +0 -0
  34. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_getters_setters.py +0 -0
  35. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_implementation_inheritance.py +0 -0
  36. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_null.py +0 -0
  37. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_orm.py +0 -0
  38. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_static.py +0 -0
  39. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_type_discrimination.py +0 -0
  40. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/__init__.py +0 -0
  41. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_constructor_code.py +0 -0
  42. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_er_name.py +0 -0
  43. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_getters_setters.py +0 -0
  44. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_implementation_inheritance.py +0 -0
  45. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_impure_tests.py +0 -0
  46. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_null.py +0 -0
  47. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_orm.py +0 -0
  48. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_public_methods_without_contracts.py +0 -0
  49. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_static.py +0 -0
  50. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_type_discrimination.py +0 -0
  51. {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/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,184 @@
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.0] - 2024-06-28
9
+
10
+ ### 🚀 Major Enhancements
11
+
12
+ #### Enhanced Mutable Objects Detection
13
+
14
+ - **Expanded EO008**: Complete rewrite from single checker to comprehensive modular package
15
+ - **12 New Error Codes**: Added EO015-EO027 for granular mutability detection
16
+ - `EO015`: Mutable class attribute violations
17
+ - `EO016`: Mutable instance attribute violations
18
+ - `EO017`: Instance attribute mutation violations
19
+ - `EO018`: Augmented assignment mutation violations
20
+ - `EO019`: Mutating method call violations
21
+ - `EO020`: Subscript assignment mutation violations
22
+ - `EO021`: Chained mutation violations
23
+ - `EO022`: Missing factory methods violations
24
+ - `EO023`: Mutable default argument violations
25
+ - `EO024`: Missing immutability enforcement violations
26
+ - `EO025`: Copy-on-write violations
27
+ - `EO026`: Aliasing violations (exposing mutable state)
28
+ - `EO027`: Defensive copy violations
29
+
30
+ #### Modular Architecture
31
+
32
+ - **6 Specialized Checkers**: Organized mutable objects detection into focused modules
33
+ - `core.py`: Main orchestrator coordinating all sub-checkers
34
+ - `contract_checker.py`: Immutability contract enforcement
35
+ - `copy_on_write_checker.py`: Copy-on-write pattern validation
36
+ - `deep_checker.py`: Cross-class mutation analysis
37
+ - `factory_checker.py`: Factory method pattern validation
38
+ - `pattern_detectors.py`: Aliasing and defensive copy detection
39
+ - `shared_state_checker.py`: Shared mutable state detection
40
+
41
+ ### 🔧 Code Quality & Complexity Rules
42
+
43
+ #### Ruff Integration
44
+
45
+ - **McCabe Complexity**: Added `C90` with max-complexity = 10
46
+ - **Nested Blocks**: Added `PLR1702` with max-nested-blocks = 5
47
+ - **Set Literals**: Added `PLR6201` enforcement (tuple → set membership tests)
48
+ - **Preview Mode**: Enabled ruff preview for latest pylint rules
49
+
50
+ #### Code Refactoring
51
+
52
+ - **Reduced Complexity**: Refactored methods from 6-7 nesting levels to ≤5
53
+ - **Extract Methods**: Improved maintainability through helper method extraction
54
+ - **Static Methods**: Added `@staticmethod` decorators where appropriate
55
+ - **Type Safety**: Enhanced mypy compliance with proper type narrowing
56
+
57
+ ### 📈 Testing & Coverage
58
+
59
+ #### Coverage Improvements
60
+
61
+ - **90% Coverage**: Increased from 86% to 90% (+5% improvement)
62
+ - **Pattern Detectors**: From 0% to 99% coverage
63
+ - **New Test Cases**: Added 4 comprehensive test cases for EO026/EO027
64
+ - **Integration Tests**: Enhanced end-to-end testing for new checkers
65
+
66
+ #### Test Quality
67
+
68
+ - **100 Tests**: All tests pass with enhanced coverage
69
+ - **False Positive Prevention**: Added tests ensuring clean code doesn't trigger violations
70
+ - **Real-world Scenarios**: Added aliasing and defensive copy test cases
71
+
72
+ ### 🏗️ Architecture Improvements
73
+
74
+ #### Type Safety
75
+
76
+ - **MyPy Clean**: Fixed all type errors with proper AsyncFunctionDef support
77
+ - **Type Assertions**: Added strategic type narrowing for complex AST patterns
78
+ - **Null Safety**: Enhanced null checking for optional attributes
79
+
80
+ #### Documentation
81
+
82
+ - **Updated README**: Comprehensive error code documentation with examples
83
+ - **Project Structure**: Updated to reflect new modular architecture
84
+ - **Coverage Badge**: Updated to reflect improved test coverage (90%)
85
+
86
+ ### 🐛 Bug Fixes
87
+
88
+ - **Pattern Detector Integration**: Fixed unused pattern detectors (0% → 99% coverage)
89
+ - **Type Compatibility**: Resolved mypy errors with union types
90
+ - **Whitespace Handling**: Consistent code formatting across all files
91
+
92
+ ### ⚠️ Breaking Changes
93
+
94
+ - **Mutable Objects**: EO008 now focuses specifically on dataclass violations
95
+ - **New Error Codes**: Code that was previously missed may now trigger EO015-EO027
96
+
97
+ ### 🔄 Internal Changes
98
+
99
+ - **Ruff Configuration**: Added comprehensive linting rules with complexity enforcement
100
+ - **Parent Tracking**: Enhanced AST analysis with parent node mapping
101
+ - **Modular Imports**: Reorganized imports for better dependency management
102
+
103
+ ---
104
+
105
+ ## [1.0.0] - 2024-06-21
106
+
107
+ ### 🎉 Initial Release
108
+
109
+ 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.
110
+
111
+ ### ✨ Features
112
+
113
+ #### Error Codes Implemented
114
+
115
+ - **EO001-EO004**: No "-er" naming principle violations
116
+
117
+ - `EO001`: Class names ending in "-er" (Manager, Controller, Handler, etc.)
118
+ - `EO002`: Method names ending in "-er" (process → processing)
119
+ - `EO003`: Variable names ending in "-er" (parser → arguments)
120
+ - `EO004`: Function names ending in "-er" (analyzer → analysis)
121
+
122
+ - **EO005**: No null (None) usage - prevents defensive programming and unclear contracts
123
+
124
+ - **EO006**: No code in constructors - constructors should only assemble objects, not execute logic
125
+
126
+ - **EO007**: No getters/setters - prevents data container anti-pattern, promotes behavioral objects
127
+
128
+ - **EO008**: No mutable objects - enforces immutability for thread-safety and predictability
129
+
130
+ - **EO009**: No static methods - promotes proper object-oriented design over procedural programming
131
+
132
+ - **EO010**: No type discrimination - prevents isinstance/type casting that violates polymorphism
133
+
134
+ - **EO011**: No public methods without contracts - requires Protocol/ABC contracts for public methods
135
+
136
+ - **EO012**: Test methods should only contain assertThat statements - enforces focused, single-assertion tests
137
+
138
+ - **EO013**: No ORM/ActiveRecord patterns - prevents mixing persistence with business logic
139
+
140
+ - **EO014**: No implementation inheritance - promotes composition over inheritance
141
+
142
+ #### 🔧 Technical Details
143
+
144
+ ##### Requirements
145
+
146
+ - Python 3.10+
147
+ - AST-based analysis for accurate code inspection
148
+ - Zero runtime dependencies beyond Python standard library
149
+
150
+ ##### Code Quality
151
+
152
+ - **86% Test Coverage**: Comprehensive test suite with real-world examples
153
+ - **Type Safety**: Full mypy type checking compliance
154
+ - **Code Style**: Ruff formatting and linting
155
+ - **Documentation**: Extensive examples and principle explanations
156
+
157
+ ##### Performance
158
+
159
+ - **Efficient AST Traversal**: Optimized single-pass analysis
160
+ - **Minimal Memory Usage**: Lightweight violation tracking
161
+ - **Fast Execution**: Suitable for large codebases
162
+
163
+ #### 🧪 Testing
164
+
165
+ - **14 Test Modules**: One dedicated test file per principle
166
+ - **Real-world Examples**: Test cases based on actual code patterns
167
+ - **Integration Tests**: End-to-end plugin functionality verification
168
+ - **Continuous Testing**: Automated test execution with coverage reporting
169
+
170
+ #### 🚀 Usage Examples
171
+
172
+ ```bash
173
+ # Install
174
+ pip install flake8-elegant-objects
175
+
176
+ # Use as flake8 plugin
177
+ flake8 --select=EO your_code/
178
+
179
+ # Run standalone
180
+ python -m flake8_elegant_objects your_code/
181
+
182
+ # Configuration
183
+ echo "[flake8]\nselect = E,W,F,EO" > .flake8
184
+ ```
@@ -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.0
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
  ```
@@ -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
 
@@ -233,7 +233,7 @@ class NoErName:
233
233
  return []
234
234
 
235
235
  # Skip common property patterns
236
- if node.name in ("property", "getter", "setter"):
236
+ if node.name in {"property", "getter", "setter"}:
237
237
  return []
238
238
 
239
239
  # Check for procedural verbs