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.
- flake8_elegant_objects-1.1.0/.claude/settings.local.json +8 -0
- flake8_elegant_objects-1.1.0/CHANGELOG.md +184 -0
- flake8_elegant_objects-1.1.0/CLAUDE.md +96 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/PKG-INFO +67 -23
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/README.md +65 -21
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/base.py +24 -4
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_er_name.py +1 -1
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_impure_tests.py +48 -38
- flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/__init__.py +5 -0
- flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/base.py +57 -0
- flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/contract_checker.py +76 -0
- flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/copy_on_write_checker.py +60 -0
- flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/core.py +306 -0
- flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/deep_checker.py +130 -0
- flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/factory_checker.py +91 -0
- flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/pattern_detectors.py +134 -0
- flake8_elegant_objects-1.1.0/flake8_elegant_objects/no_mutable_objects/shared_state_checker.py +32 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_public_methods_without_contracts.py +1 -1
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/pyproject.toml +12 -4
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_integration.py +7 -6
- flake8_elegant_objects-1.1.0/tests/test_no_mutable_objects.py +468 -0
- flake8_elegant_objects-1.0.0/CLAUDE.md +0 -59
- flake8_elegant_objects-1.0.0/flake8_elegant_objects/no_mutable_objects.py +0 -69
- flake8_elegant_objects-1.0.0/tests/test_no_mutable_objects.py +0 -167
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/.github/workflows/lint.yml +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/.github/workflows/publish.yml +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/.github/workflows/test.yml +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/.gitignore +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/LICENSE +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/docs/EO011.md +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/__init__.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/__main__.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_constructor_code.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_getters_setters.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_implementation_inheritance.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_null.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_orm.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_static.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/no_type_discrimination.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/__init__.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_constructor_code.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_er_name.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_getters_setters.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_implementation_inheritance.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_impure_tests.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_null.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_orm.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_public_methods_without_contracts.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_static.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/tests/test_no_type_discrimination.py +0 -0
- {flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/uv.lock +0 -0
|
@@ -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.
|
|
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:
|
|
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
|
[](https://github.com/AntonProkopyev/flake8-elegant-objects)
|
|
34
|
-
[](https://github.com/AntonProkopyev/flake8-elegant-objects)
|
|
35
35
|
[](https://python.org)
|
|
36
36
|
[](LICENSE)
|
|
37
37
|
[](https://github.com/astral-sh/ruff)
|
|
38
38
|
[](https://mypy.readthedocs.io/)
|
|
39
39
|
[](https://flake8.pycqa.org/)
|
|
40
40
|
|
|
41
|
-
Detects violations of
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
121
|
-
- ❌ `items
|
|
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
|
|
204
|
-
├──
|
|
205
|
-
├──
|
|
206
|
-
├──
|
|
207
|
-
├──
|
|
208
|
-
├── no_getters_setters.py
|
|
209
|
-
├──
|
|
210
|
-
├──
|
|
211
|
-
├──
|
|
212
|
-
├──
|
|
213
|
-
├──
|
|
214
|
-
├──
|
|
215
|
-
|
|
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
|
[](https://github.com/AntonProkopyev/flake8-elegant-objects)
|
|
4
|
-
[](https://github.com/AntonProkopyev/flake8-elegant-objects)
|
|
5
5
|
[](https://python.org)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://github.com/astral-sh/ruff)
|
|
8
8
|
[](https://mypy.readthedocs.io/)
|
|
9
9
|
[](https://flake8.pycqa.org/)
|
|
10
10
|
|
|
11
|
-
Detects violations of
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
91
|
-
- ❌ `items
|
|
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
|
|
174
|
-
├──
|
|
175
|
-
├──
|
|
176
|
-
├──
|
|
177
|
-
├──
|
|
178
|
-
├── no_getters_setters.py
|
|
179
|
-
├──
|
|
180
|
-
├──
|
|
181
|
-
├──
|
|
182
|
-
├──
|
|
183
|
-
├──
|
|
184
|
-
├──
|
|
185
|
-
|
|
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
|
```
|
{flake8_elegant_objects-1.0.0 → flake8_elegant_objects-1.1.0}/flake8_elegant_objects/base.py
RENAMED
|
@@ -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
|
|
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
|
|
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
|
|